diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..a8ac58eacae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+
+# Bin File
+/bin/
diff --git a/README.md b/README.md
index 16208adb9b6..271b29bf5d8 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+# MedConnect
+
+[![CI Status](https://github.com/AY2425S1-CS2103T-T13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-T13-1/tp/actions)
+[![codecov](https://codecov.io/gh/AY2425S1-CS2103T-T13-1/tp/graph/badge.svg)](https://codecov.io/github/AY2425S1-CS2103T-T13-1/tp)
![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.
+**MedConnect is a desktop application for healthcare administrators in old folks homes for dementia patients to consolidate contacts of patients and related information into a single database.**
+
+For the detailed documentation of this project, see the **[MedConnect Product Website](https://ay2425s1-cs2103t-t13-1.github.io/tp/)**.
+
+* If you are interested in using MedConnect, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing MedConnect, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+
+**Acknowledgements**
+
+This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+
+* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
diff --git a/build.gradle b/build.gradle
index 0db3743584e..b88809023a1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,18 @@ plugins {
id 'jacoco'
}
-mainClassName = 'seedu.address.Main'
+application {
+ mainClass = 'seedu.address.Main'
+}
+
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
+jacoco {
+ toolVersion = "0.8.8"
+}
+
repositories {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
@@ -20,6 +27,10 @@ checkstyle {
toolVersion = '10.2'
}
+run {
+ enableAssertions = true
+}
+
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
@@ -66,7 +77,7 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'medconnect.jar'
}
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index ff3f04abd02..709e469542b 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,41 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Alden Tan
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
-
-* Role: Project Advisor
-
-### Jane Doe
-
-
-
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/aldentantan)]
* Role: Team Lead
-* Responsibilities: UI
+* Responsibilities: Deliverables and Deadlines, Scheduling and Tracking
-### Johnny Doe
+### Kelly Wang Sze Qing
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](https://github.com/kellywsq03)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Testing & Code Quality
-### Jean Doe
+### Saajid Shaik
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/saajidshaik02)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Documentation, SourceView Expert
-### James Doe
+### Wong Zhian John
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+
+[[github](http://github.com/johnwz123)]
+
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Integration + Code Quality
+
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 743c65a49d2..2e144e86744 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -7,9 +7,16 @@ title: Developer Guide
--------------------------------------------------------------------------------------------------------------------
+
+
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
+* Libraries used: JavaFX, Jackson, JUnit5
+* The undo and redo features were inspired by the proposed implementation found in [AB3's Developer Guide](https://se-education.org/addressbook-level3/DeveloperGuide.html#proposed-undoredo-feature).
+* The icons used were taken from [Flaticon](https://www.flaticon.com/).
+* Use of GPT for auto-complete tool feature (A0272009L Saajid)
+> Mutliple bugs and issues due to interaction between suggestion and autocompleting -> GPT generated code which broke down current code into multiple functions and structured for individual purposes. Thereafter, the code was modified to resolved more specific exepected outcomes to suit the needs of the app. (ChatGPT makes multiple serious mistakes, it CANNOT implement this feature AT ALL without substantial amount of human intervention)
--------------------------------------------------------------------------------------------------------------------
@@ -19,11 +26,27 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
--------------------------------------------------------------------------------------------------------------------
+## **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)
+
+--------------------------------------------------------------------------------------------------------------------
+
+
+
## **Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+
+
+
+:information_source: **Note:** Due to a limitation of PlantUML, the destroy marker (X) for lifelines in sequence diagrams cannot be displayed at the correct position. As a workaround, the lifelines are extended to the end of the diagram.
### Architecture
@@ -32,11 +55,13 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
The ***Architecture Diagram*** given above explains the high-level design of the App.
+
+
Given below is a quick overview of main components and how they interact with each other.
**Main components of the architecture**
-**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
+**`Main`** (consisting of classes [`Main`](https://github.com/AY2425S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2425S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
@@ -58,7 +83,7 @@ The *Sequence Diagram* below shows how the components interact with each other f
Each of the four main components (also shown in the diagram above),
* defines its *API* in an `interface` with the same name as the Component.
-* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.
+* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point).
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
@@ -66,31 +91,37 @@ For example, the `Logic` component defines its API in the `Logic.java` interface
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-T13-1/tp/blob/master/src/main/java/seedu/address/ui/Ui.java)
![Structure of the UI Component](images/UiClassDiagram.png)
The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` 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-T13-1/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2425S1-CS2103T-T13-1/tp/blob/master/src/main/resources/view/MainWindow.fxml)
-The `UI` component,
+The `UI` component:
* executes user commands using the `Logic` component.
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* 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`.
+
+
### 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-T13-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.
![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
@@ -106,6 +137,8 @@ How the `Logic` component works:
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.
1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
+
+
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
@@ -114,12 +147,13 @@ How the parsing works:
* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
+
+
### 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-T13-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).
@@ -127,16 +161,19 @@ The `Model` component,
* 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.
+
### Storage component
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/AY2425S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/storage/Storage.java)
@@ -151,13 +188,65 @@ Classes used by multiple components are in the `seedu.address.commons` package.
--------------------------------------------------------------------------------------------------------------------
+
+
## **Implementation**
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Add feature
+
+#### Implementation
+
+The image below shows the class diagram of a Person object and its related class attributes.
+
+![Person Class Diagram](images/PersonClassDiagram.png)
+
+The Person object is made up of several attributes:
+* `Name`: The name of the patient.
+* `Phone`: The phone number of the patient.
+* `Email`: The email of the patient.
+* `Address`: The address of the patient.
+* `Doctor`: The doctor assigned to the patient.
+* `Emergency Contact`: A list of emergency contacts of the patient.
+* `Tags`: Additional information about the patient.
+
+The Doctor object is also made up of attributes:
+* `Doctor Name`: The name and title of the doctor.
+* `Phone`: The phone number of the doctor.
+* `Email`: The email of the doctor.
+
+The Emergency Contact object is also made up of attributes:
+* `Name`: The name of the emergency contact.
+* `Phone`: The phone number of the emergency contact.
+* `Relationship`: The relationship of the emergency contact to the patient.
+
+
-#### Proposed Implementation
+#### Feature details
+
+1. MedConnect will verify that the parameters supplied by the user follow a set of relevant restrictions for the respective parameters.
+2. If any invalid parameter is provided, an error will be thrown, informing the user which parameter violates the restrictions. The format for the valid input for that parameter will be displayed to the user.
+3. If all parameters are valid, a new `Person` entry will be created and stored in the `VersionedAddressBook`.
+
+#### Design Considerations:
+
+**Aspect: The required input of parameters:**
+
+* **Alternative 1 (current choice):** Make all parameters compulsory, except Tags.
+ * Pros: Will not have missing data when it is needed in an emergency.
+ * Cons: Add Command is lengthy to type out, might be hard to remember the syntax.
+* **Alternative 2:** Make only a few specific parameters compulsory.
+ * Pros: Patient registration will be faster.
+ * Cons: If user forgets to update missing details, during an emergency there might not be an emergency contact to call.
+
+We opted for Alternative 1 to make almost all parameters compulsory as the autocomplete feature we implemented will aid users in typing out the Add Command.
+
+
+
+### Undo/redo feature
+
+#### Implementation
The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
@@ -173,6 +262,8 @@ Step 1. The user launches the application for the first time. The `VersionedAddr
![UndoRedoState0](images/UndoRedoState0.png)
+
+
Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
![UndoRedoState1](images/UndoRedoState1.png)
@@ -185,6 +276,8 @@ Step 3. The user executes `add n/David …` to add a new person. The `add` co
+
+
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
![UndoRedoState3](images/UndoRedoState3.png)
@@ -194,6 +287,8 @@ than attempting to perform the undo.
+
+
The following sequence diagram shows how an undo operation goes through the `Logic` component:
![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png)
@@ -220,6 +315,8 @@ Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Sinc
![UndoRedoState5](images/UndoRedoState5.png)
+
+
The following activity diagram summarizes what happens when a user executes a new command:
@@ -237,98 +334,656 @@ The following activity diagram summarizes what happens when a user executes a ne
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
+
-### \[Proposed\] Data archiving
+### Data archiving
-_{Explain here how the data archiving feature will be implemented}_
+#### Implementation
+The archive functionality in MedConnect is facilitated by the `ModelManager` class. It handles the archiving, listing, loading, and deleting of archived contact data. The `ModelManager` interacts with the `Filename` class and `FileUtil` components to manage the archive files in the archive directory.
---------------------------------------------------------------------------------------------------------------------
+For example, the sequence diagram below illustrates the interactions within the `ModelManager` component when the `archive` command is executed.
-## **Documentation, logging, testing, configuration, dev-ops**
+![ArchiveSequenceDiagram.png](images%2FArchiveSequenceDiagram.png)
-* [Documentation guide](Documentation.md)
-* [Testing guide](Testing.md)
-* [Logging guide](Logging.md)
-* [Configuration guide](Configuration.md)
-* [DevOps guide](DevOps.md)
+1. The `ArchiveCommand` archives the current address book data by calling the `archiveAddressBook` method in the `ModelManager` component.
+2. The `ModelManager` creates the archive directory if it does not exist.
+3. The `ModelManager` saves the current address book data to a JSON file in the archive directory with the specified file name.
--------------------------------------------------------------------------------------------------------------------
+
+
## **Appendix: Requirements**
-### Product scope
+### Product Scope
+
+#### Target User Profile
+
+- **User Role:** Healthcare Administrator
+- **Workplace:** Elderly care home for dementia patients
+- **Responsibilities:**
+ - Manage and update contact details for patients, doctors, and next-of-kin.
+ - Respond quickly to emergency situations by accessing relevant contacts.
+ - Maintain communication efficiency with minimal manual processes.
-**Target user profile**:
+- **Key Characteristics:**
+ - Handles large volumes of contact data on a daily basis.
+ - Prefers using desktop applications over mobile or web-based alternatives.
+ - Skilled at typing and prefers keyboard shortcuts over mouse interactions for speed.
+ - Comfortable with using command-line interfaces (CLI) for fast data entry and retrieval.
-* 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
+#### Value Proposition
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+MedConnect offers a **streamlined contact management system** tailored for healthcare administrators. Its key features include:
+- **Efficient Lookup and Update:** Quickly find and update contact information for patients, their emergency contacts, and healthcare staff.
+- **Time-Sensitive Operations:** When every second counts, MedConnect ensures that administrators can contact the right person immediately.
+- **Command-Line First:** Optimized for users who prefer CLI, allowing for rapid data entry and navigation without reliance on graphical interfaces.
+- **Comprehensive Contact Database:** Centralizes all relevant contact details, reducing the need for multiple systems and improving response times in emergencies.
+
+---
+
+
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+#### Beginner User Stories
+
+| Priority | As a … | I want to … | So that I can… |
+|----------|--------------------------|-------------------------------------------------|--------------------------------------------------------------------------------|
+| `* * *` | new user | have sample data to work with | understand how to use the application |
+| `* * *` | healthcare administrator | add new doctors and patients | easily reach out to them when needed |
+| `* * *` | healthcare administrator | update contact details | ensure all contact information is accurate and current |
+| `* * *` | healthcare administrator | delete outdated patient contacts | ensure all information is relevant and current |
+| `* * *` | healthcare administrator | view all contacts in the address book | have a comprehensive overview of all patients, doctors, and emergency contacts |
+| `* * *` | healthcare administrator | view patient emergency contact details | notify next-of-kin during urgent medical events |
+| `* * *` | healthcare administrator | add emergency contacts for patients | quickly reach out to next-of-kin during medical emergencies |
+| `* * *` | healthcare administrator | assign doctors to patients | easily track which doctor is responsible for each patient |
+| `* *` | healthcare administrator | search contacts by name or assigned doctor | quickly find and connect with the right person in high-pressure situations |
+| `* *` | healthcare administrator | add multiple emergency contacts for each person | reach different emergency contacts when one is uncontactable |
+
+
-*{More to be added}*
+#### Intermediate User Stories
+
+| Priority | As a … | I want to … | So that I can… |
+|----------|--------------------------|---------------------------------------|------------------------------------------------------------------------------|
+| `* *` | healthcare administrator | filter contacts by their doctor | view a consolidated list of all the patients a doctor is responsible for |
+| `* *` | healthcare administrator | sort patients by their admission time | provide appropriate care to longer-term patients |
+| `* *` | healthcare administrator | tag important notes to patients | remember special considerations about certain patients |
+| `* *` | healthcare administrator | archive outdated contacts | maintain a clean and relevant contact list without losing historical records |
+| `* *` | healthcare administrator | load backup archived data | restore a backup copy in case of data corruption or user error |
+| `* *` | healthcare administrator | delete archived data | free up storage space and remove unnecessary or outdated contact information |
+| `* *` | healthcare administrator | view a list of all archived data | keep track of the archived data for reference or auditing purposes |
+| `* *` | healthcare administrator | undo the last operation | recover from accidental deletions or modifications |
+| `* *` | healthcare administrator | redo the last undone operation | reverse an undo operation if it was done in error |
+
+
+
+#### Advanced User Stories
+
+| Priority | As a … | I want to … | So that I can… |
+|----------|--------------------------|-----------------------------------------------|----------------------------------------------------------------------|
+| `* *` | healthcare administrator | import contact data in bulk | keep the database up-to-date without manual entry |
+| `* *` | healthcare administrator | export contact information | provide it to others or have a backup in case of system failures |
+
+---
+
+
### Use cases
+Not all commands use cases are included as commands, such as `clear` and `exit` are self-explanatory.
+
(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+#### Use Case: Add a New Contact
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to add a new patient contact.
+2. MedConnect prompts the user to enter patient details:
+ - Name
+ - Phone Number
+ - Address
+ - Email
+ - Doctor Name
+ - Doctor Phone Number
+ - Doctor Email
+ - Emergency Contact Name
+ - Emergency Contact Phone Number
+ - Emergency Contact Relationship to Patient
+ - Tag(s) [Optional]
+3. User enters the required details.
+4. MedConnect validates the provided information.
+5. MedConnect successfully adds the new contact to the address book.
+6. MedConnect confirms that the contact was added successfully.
+
+ **Use case ends.**
+
+
+
+**Extensions:**
+
+**3a.** The entered details are invalid (e.g., phone number contains letters).
+- **3a1.** MedConnect informs the user of the invalid details.
+- **3a2.** User corrects the invalid details and resubmits.
+
+ **Use case resumes from step 4.**
+
+**3b.** Some required fields are missing.
+- **3b1.** MedConnect prompts the user to complete the missing fields.
+- **3b2.** User provides the missing information.
+
+ **Use case resumes from step 4.**
+
+**3c.** There are duplicate fields provided (e.g., 2 name fields).
+- **3c1.** MedConnect informs the user of the duplicate field(s).
+- **3c2.** User removes the duplicate parameters and resubmits.
+
+ **Use case resumes from step 4.**
+
+**6a.** The contact already exists in the system (e.g., duplicate phone number).
+- **6a1.** MedConnect notifies the user of the duplicate entry.
+
+ **Use case ends.**
+
+---
+
+
+
+#### Use Case: Edit a Contact
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+
+**Main Success Scenario (MSS):**
+1. User requests to list all contacts.
+2. MedConnect retrieves and shows a list of all contacts.
+3. User requests to edit a patient's details.
+4. MedConnect prompts the user to enter the following details:
+ - Index of the patient to be edited
+ - New data of the field(s) to be edited
+5. MedConnect validates the provided information.
+6. MedConnect successfully edits the contact with the new data.
+7. MedConnect confirms that the contact was edited successfully.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**2a.** The patient list is empty.
+
+ - **Use case ends.**
+
+**4a.** The given index is invalid (e.g., out of range).
+- **4a1.** MedConnect informs the user of the invalid index.
+
+ **Use case resumes from step 3.**
+
+**4b.** The entered details are invalid (e.g., phone number contains letters).
+- **4b1.** MedConnect informs the user of the invalid details.
+- **4b2.** User corrects the invalid details and resubmits.
+
+ **Use case resumes from step 5.**
+
+---
+
+
+
+#### Use Case: Delete a Contact
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+
+**Main Success Scenario (MSS):**
+1. User requests to list all contacts.
+2. MedConnect retrieves and shows a list of all contacts.
+3. User requests to delete a specific contact by its index.
+4. MedConnect removes the contact from the database.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**2a.** The contact list is empty.
+
+
+- **Use case ends.**
+
+
+**3a.** The given index is invalid (e.g., out of range).
+- **3a1.** MedConnect informs the user of the invalid index.
+
+ **Use case resumes from step 2.**
+
+---
+
+
+
+#### Use Case: Add Emergency Contacts
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to list all contacts.
+2. MedConnect retrives and shows a list of all contacts.
+3. User requests to add a new emergency contact to a patient.
+4. MedConnect prompts the user to enter the following details:
+ - Index of the patient
+ - Emergency Contact Name
+ - Emergency Contact Phone Number
+ - Emergency Contact Relationship to Patient
+5. User enters the required details.
+6. MedConnect adds the emergency contact to the patient's details.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**2a.** The contact list is empty.
+
+- **Use case ends.**
+
+**5a.** The index given is out of range.
+- **5a1.** MedConnect informs the user of the invalid index.
+
+ **Use case resumes from step 3.**
+
+**5b.** The emergency contact already exists under the patient. (e.g., duplicate phone number).
+- **5b1.** MedConnect notifies the user that the emergency contact already exists.
+
+ **Use case ends.**
+
+**5c.** The emergency contact relationship provided is invalid.
+- **5c1.** MedConnect informs the user of possible relationship types.
+- **5c2.** User corrects the invalid relationship type and resubmits.
+
+ **Use case resumes from step 6.**
+
+---
+
+#### Use Case: Delete an Emergency Contact
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+
+**Main Success Scenario (MSS):**
+1. User requests to list all contacts.
+2. MedConnect retrieves and shows a list of all contacts.
+3. User requests to delete a specific emergency contact of a patient by its index.
+4. MedConnect removes the emergency contact from the database.
+
+ **Use case ends.**
+
+
+**Extensions:**
+
+**2a.** The contact list is empty.
+
+- **Use case ends.**
+
+
+**3a.** The given index is invalid (e.g., out of range).
+- **3a1.** MedConnect informs the user of the invalid index.
+
+ **Use case resumes from step 2.**
+
+**3b.** The specified patient only has 1 emergency contact.
+- **3b1.** MedConnect informs the user that they cannot delete a patient's only emergency contact.
+
+ **Use case ends.**
+
+---
+
+
+
+#### Use Case: Find Contacts By Patient Name
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to find a patient by their name.
+2. MedConnect prompts the user to provide a name to search for.
+3. User provides a name.
+4. MedConnect returns a list of patients who matches the provided name.
+
+ **Use case ends.**
-**MSS**
+**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
+**3a.** User provides a blank name.
+- **3a1.** MedConnect notifies the user to provide a name.
- Use case ends.
+ **Use case resumes from step 3.**
-**Extensions**
+**4a.** There are no patients who match the provided name.
+- **4a1.** MedConnect returns a list of 0 patients.
-* 2a. The list is empty.
+ **Use case ends.**
- Use case ends.
+---
+
+
+
+#### Use Case: Find Contacts By Doctor Name
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to find a patient by their assigned doctor's name.
+2. MedConnect prompts the user to provide a name to search for.
+3. User provides a name.
+4. MedConnect returns a list of patients whose assigned doctor matches the provided name.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**3a.** User provides a blank name.
+- **3a1.** MedConnect notifies the user to provide a name.
+
+ **Use case resumes from step 3.**
+
+**4a.** There are no patients whose doctor matches the provided name.
+- **4a1.** MedConnect returns a list of 0 patients.
+
+ **Use case ends.**
+
+---
+
+
+
+#### Use Case: Archive Contacts
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to archive the address book with a description.
+2. MedConnect confirms that the contact data has been successfully archived.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**1a.** The given description is invalid.
+- **1a1.** MedConnect informs the user of the invalid description.
+- **1a2.** User corrects the invalid description and resubmits.
+
+ **Use case resumes from step 2.**
+
+---
+
+#### Use Case: List Archive Files
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to list all archived data files in the archive folder.
+2. MedConnect returns a list of all the archived data files in the archive folder.
+
+ **Use case ends.**
+
+---
+
+
+
+#### Use Case: Load Archived Contacts
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to load an archive file.
+2. MedConnect prompts the user to provide the file name of an archive file in the archives folder.
+3. User enters a file name.
+4. MedConnect validates the file name and checks for a matching file.
+5. MedConnect loads the archived contacts from the file into the address book.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**3a.** The given file name is invalid.
+- **3a1.** MedConnect notifies the user that the file name contains invalid characters.
+- **3a2.** User corrects the file name and resubmits.
+
+ **Use case resumes from step 4.**
+
+**3b.** The given file name does not match any existing file.
+- **3b1.** MedConnect notifies the user that a file with the given file name is not found.
+- **3b2.** User enters the file name of an existing file in the archives folder and resubmits.
+
+ **Use case resumes from step 4.**
+
+---
+
+
+
+#### Use Case: Delete Archive File
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User requests to delete an archive file.
+2. MedConnect prompts the user to provide the file name of an archive file in the archives folder.
+3. User enters a file name.
+4. MedConnect validates the file name and checks for a matching file.
+5. MedConnect deletes the archive file.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**3a.** The given file name is invalid.
+- **3a1.** MedConnect notifies the user that the file name contains invalid characters.
+- **3a2.** User corrects the file name and resubmits.
+
+ **Use case resumes from step 4.**
+
+**4a.** The given file name does not match any existing file.
+- **4a1.** MedConnect notifies the user that a file with the given file name is not found.
+- **4a2.** User enters the file name of an existing file in the archives folder and resubmits.
+
+ **Use case resumes from step 4.**
+
+---
+
+
+
+#### Use Case: Undo Last Command
+
+**System:** MedConnect
+
+**Actor:** Healthcare Administrator
+
+**Main Success Scenario (MSS):**
+1. User performs an command that modifies the address book (e.g., adds or deletes a contact).
+2. User requests to undo the last command.
+3. MedConnect reverts to the state before the last command.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**2a.** There is no previous command to undo.
+- **2a1.** MedConnect informs the user that there are no actions to undo.
+
+ **Use case ends.**
+
+---
+
+
+
+#### Use Case: Redo Last Undone Command
+
+**System: MedConnect**
+
+**Actor: Healthcare Administrator**
+
+**Main Success Scenario (MSS):**
+1. User performs an undo command.
+2. User requests to redo the last undone action.
+3. MedConnect restores the previously undone action.
+
+ **Use case ends.**
+
+**Extensions:**
+
+**2a.** There is no undone command to redo.
+- **2a1.** MedConnect informs the user that there are no actions to redo.
-* 3a. The given index is invalid.
+ **Use case ends.**
- * 3a1. AddressBook shows an error message.
+**2b.** User makes a new change after an undo command.
+- **2b1.** MedConnect restores the undo command that the user most recently executed.
- Use case resumes at step 2.
+ **Use case ends.**
-*{More to be added}*
+
-### Non-Functional Requirements
+### Non-Functional Requirements (NFRs)
-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.
+#### 1. Performance Requirements:
+- **Responsiveness:**
+ The system should respond to user commands (e.g., adding, updating, or viewing contacts) within **2 seconds** for typical operations under normal usage conditions (i.e., up to 1000 contacts in the database).
-*{More to be added}*
+- **Scalability:**
+ MedConnect should support operations with **up to 5000 contacts** without performance degradation. Basic operations such as retrieving or adding contacts should not exceed a response time of **3 seconds** under this load.
+
+#### 2. Reliability and Availability:
+- **System Uptime:**
+ MedConnect must be available for use at least **99% of the time**, especially during hospital operating hours (24/7 access). Regular maintenance should be scheduled during off-peak times.
+
+- **Disaster Recovery and Backup:**
+ Contact data must be backed up **daily** to prevent data loss. The system should be able to recover from backup within **2 hours** of a failure.
+
+#### 3. Usability Requirements:
+- **Typing Efficiency:**
+ The system should be optimized for keyboard-only interactions. A user familiar with the system should be able to complete key operations (e.g., adding a new contact, viewing emergency contacts) in less than **30 seconds** using only the keyboard.
+
+- **Error Handling and Feedback:**
+ The system must provide **immediate feedback** (within 1 second) when an error occurs, such as invalid input or missing fields. The user should be able to correct errors without restarting the operation.
+
+#### 4. Data and Storage Requirements:
+- **Human-Editable File Format:**
+ Contact information should be stored in a **human-readable and editable format** (e.g., `.json` or `.csv`) so that administrators can manually access and modify data if needed.
+
+- **Data Integrity:**
+ The system must ensure that no data is lost or corrupted during common operations (e.g., adding, updating, or deleting contacts). **Transaction-like behavior** must be implemented to ensure all data operations either succeed fully or fail without partially corrupting data.
+
+#### 5. Compatibility and Portability:
+- **Cross-Platform Support:**
+ MedConnect must be compatible with **mainstream operating systems** (Windows, macOS, Linux) and function seamlessly on systems with **Java 17 or higher** installed.
+
+#### 6. Maintainability and Extensibility:
+- **Modular Design:**
+ The system must be designed with a modular structure, allowing future extensions such as additional data fields or user roles without requiring significant rework.
+
+- **Testability:**
+ MedConnect must be **easily testable**, with automated tests that can cover at least **70% of the codebase**. Each core feature (e.g., adding a contact, deleting outdated contacts) should have dedicated test cases.
+---
+
+
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+- **Mainstream OS**: Windows, Linux, Unix, MacOS
---------------------------------------------------------------------------------------------------------------------
+- **MedConnect**: A healthcare application designed to help healthcare administrators manage patient and staff contact information efficiently.
+
+- **Healthcare Administrator**: The primary user of MedConnect, responsible for managing patient and staff contact details in a healthcare environment. They ensure that the contact information is up-to-date for communication during critical situations.
+
+- **Emergency Contact**: The designated person or persons to be notified in case of a patient emergency, usually including details such as their name, phone number, and relationship to the patient.
+
+- **Primary Contact Method**: The main communication method for a person in the system, typically used for emergency situations.
+
+- **Command-Line Interface (CLI)**: A method of interacting with MedConnect through typed text commands, allowing fast input for users who prefer typing over graphical interfaces.
+
+- **Mainstream Operating Systems**: Common operating systems on which MedConnect can run, including Windows, macOS, and Linux.
+
+- **Encryption**: The process of encoding sensitive data, such as patient information, to protect it from unauthorized access.
+
+- **Java 17**: The version of Java required to run MedConnect, which ensures compatibility and performance across different operating systems.
+
+- **Human-Editable File**: A data file format (e.g., `.json` or `.csv`) that can be easily accessed and modified by healthcare administrators without needing special software.
+
+- **System Uptime**: The percentage of time that MedConnect is available and operational, measured as part of reliability goals.
+
+- **Backup**: The process of creating copies of MedConnect's data to ensure it can be restored in the event of data loss or system failure.
+
+- **Test Coverage**: The percentage of the system's code that is covered by automated tests, ensuring that key features and functionality are reliably tested.
+
+
+
+## **Appendix: Planned Enhancements**
+
+Team size: 4
+
+The current version of MedConnect has its flaws so here are our plans for future enhancements to improve future versions of MedConnect.
+
+### Optimisations to handle large number of contacts
+
+Currently, depending on the limitations of the PC hardware that MedConnect is running on, a large number of contacts may cause an OutOfMemory error. Our planned enhancement is to optimise the data storage and retrieval process to handle a larger number of contacts without causing performance issues or memory errors, or implement a pagination feature to display contacts in smaller batches. We plan to also implement an import and export feature to allow users to import and export contacts in bulk to avoid the need to manually enter each contact.
+
+### Duplicate detection
+
+Currently, MedConnect's duplicate person detection only works within each class. There is no duplicate person detection between a patient and a doctor. In reality, there should likely not be a case where a patient is also a doctor. We plan to implement duplicate person detection across classes to prevent or display a warning message to prevent a person from being added as both a patient and a doctor, for instance, in the future.
+
+### Whitespaces in names
+
+Currently, MedConnect is able to remove leading and trailing whitespaces from names. However, the functionality to remove whitespaces in between words in a name is not yet implemented. We plan to implement this in the future to prevent users from entering names with excessive whitespaces between words in the future as it may reduce readability.
+
+
+
+### Handling long fields
+
+Currently, MedConnect does not handle long fields well. For example, if a user enters a long name, the name may be cut off in the GUI. Our planned enhancement is to implement a feature that allows users to view the full details of each field by hovering over the field in the GUI or clicking to expand the details for the field in the card. This will allow users to view the full field (e.g. full name) should the field be too long to be displayed in the GUI.
+
+### Multiple Language Support
+
+Currently, MedConnect is only available for usage in English. We recognise that our target users may not be able to read English proficiently or require non-English inputs (e.g. Chinese names). Our planned enhancement is to translate MedConnect into other languages, such as Chinese, Malay and Tamil to accommodate for healthcare administrators who are more fluent in these languages and require such languages to be supported in the application.
+
+### Emergency Contact UI
+
+Currently, clicking on a emergency contact card of a patient in the GUI, followed by clicking the same patient card results in the emergency contact card being unselected. This behavior is not ideal for users who select the card to focus on viewing the correct contact in the list. Our planned enhancement is to update the behaviour of selecting the patient card so that it will not refresh the user's selection upon clicking it.
+
+### Autocomplete field suggestion
+
+Currently, due to difficulties with parsing, the autocomplete feature does not suggest square brackets for optional fields, such as in the Edit command. Users would have to refer to the User Guide or error message to know which fields are optional. We plan to add the square brackets to clearly indicate optional parameters in the autocomplete feature in future iterations of MedConnect to minimise the need for users to continuously reference the User Guide.
+
+
+
+### Autocomplete dynamic parsing
+
+Currently, the autocomplete feature does not dynamically parse the user's input for each parameter as it is entered to provide the next suggestion. For example, the autocomplete feature would not function ideally if users enter a whitespace after command prefixes (e.g., `add n/ John` would continue to suggest `add n/ John (n/NAME)`). This also limits us to keep to the specified default order of parameters. We plan to implement dynamic parsing in the autocomplete feature to provide more accurate suggestions based on the user's input in future iterations of MedConnect, particularly for optional parameters. This would also improve the user experience by allowing us to carry out input validation for each parameter and provide feedback to the user in real-time without the need to submit the command.
+
+
## **Appendix: Instructions for manual testing**
@@ -343,9 +998,11 @@ testers are expected to do more *exploratory* testing.
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ 1. Download the latest jar file [here](https://github.com/AY2425S1-CS2103T-T13-1/tp/releases) and copy into an empty folder.
+
+ 1. Open a terminal window and `cd` into the same folder.
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Enter `java -jar medconnect.jar` into the terminal. Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum but it is resizable.
1. Saving window preferences
@@ -354,29 +1011,149 @@ testers are expected to do more *exploratory* testing.
1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+1. Shutdown
-### Deleting a person
+ There are multiple ways to exit the application:
-1. Deleting a person while all persons are being shown
+ 1. Use the `exit` command.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Click `File` in the top left corner, then `Exit` in the dropdown menu.
- 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. Click the red 'X' of the application window.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+ 1. Use the keyboard shortcut `Alt + F4`.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+
-1. _{ more test cases … }_
+### Adding a patient
+
+1. Adding a patient while any number of patients are being shown.
+
+ **Note:** Due to the long length of valid `add` commands, the usage of `xx/PARAMETER...` will refer to all remaining compulsory parameters that have not been mentioned for that test case, with valid inputs for the respective parameters.
+ **Prerequisites:** List all patients using the `list` command. Patients are sorted by the time they were added to MedConnect.
+
+
+ | Test case input | Expected behaviour | Expected message |
+ |--------------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
+ | `add n/Ryan p/98765432 xx/PARAMETER...` | A new patient Ryan is added to the bottom of the patient list. | New person added: [PERSON DETAILS] |
+ | `add n/Ryan n/Daniel p/98765432 xx/PARAMETER...` | Error message is shown. | Multiple values specified for the following single-valued field(s): n/ |
+ | `add n/Ryan` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `add` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `add n/` | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `add p/???` | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `add n/John+Doe xx/PARAMETER...` | Error message is shown | Names should not be blank and should only contain alphanumeric characters, spaces or the following special characters: - . ( ) @ / ' |
+ | `add p/98@1532 xx/PARAMETER...` | Error message is shown | Phone numbers should only contain numbers, and it should be at least 3 digits long |
+ | `add ecrs/knight xx/PARAMETER...` | Error message is shown | Relationship type should be Parent, Child, Sibling, Spouse, Grandparent or Relative or their gendered variants |
+
+
+
+
+
+### Editing a patient
+
+1. Editing a patient while any number of patients are being shown.
+
+ **Prerequisites:** List all patients using the `list` command. Patients are sorted by the time they were added to MedConnect.
+
+
+ | Test case input | Expected behaviour | Expected message |
+ |--------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
+ | `edit 1 n/Ryan p/98765432 e/ryan@hotmail.com` | The name, phone and email of the first patient in the list is edited to the new values provided as arguments | Edited person: [PERSON DETAILS] |
+ | `edit 1` | Error message is shown. | At least one field to edit must be provided. |
+ | `edit 1 n/` | Error message is shown. | Names should not be blank and should only contain alphanumeric characters, spaces or the following special characters: - . ( ) @ / ' |
+ | `edit 1 n/John p/` | Error message is shown. | Phone numbers should only contain numbers, and it should be at least 3 digits long |
+ | `edit` | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `edit 2 ecname/John Doe` | Error message is shown | At least one emergency contact index to edit must be provided. |
+ | `edit 2 ec/2` | Error message is shown | At least one emergency contact field to edit must be provided. |
+ | `edit 1 ec/x ecname/Heather ecphone/5137985 ecrs/Sibling` (x > number of emergency contacts) | Error message is shown | Index is not a non-zero unsigned integer. |
+ | `edit x n/Heather` (x > number of contacts) | Error message is shown | The person index provided is invalid |
+
+
+
+### Deleting a patient
+
+1. Deleting a patient while all patients are being shown
+
+
+ **Prerequisites:**
+ 1. List all patients using the `list` command.
+ 2. Multiple persons in the list.
+
+
+ | Test case input | Expected behaviour | Expected message |
+ |------------------------------------------------------|--------------------------------------------------------------------------|--------------------------------------------------|
+ | `delete 1` | First contact is deleted from the list. | Deleted Person: [PERSON DETAILS] |
+ | `delete 1 ec/1` | The first emergency contact of the first contact in the list is deleted. | Deleted emergency contact: [PERSON DETAILS] |
+ | `delete 0` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `delete 2 ec/0` | Error message is shown. | Index is not a non-zero unsigned integer. |
+ | `delete ec/1` | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `delete ec/x` (x > number of emergency contacts) | Error message is shown | The emergency contact index provided is invalid |
+ | `delete x` (x > number of contacts) | Error message is shown | The person index provided is invalid |
+
+
+
+
+2. Deleting a patient while a filtered list is being shown
+
+
+ **Prerequisites:** The patient list is filtered using the `find` or `finddoc` command.
+
+
+ | Test case input | Expected behaviour | Expected message |
+ |------------------------------------------------------|--------------------------------------------------------------------------|--------------------------------------------------|
+ | `delete 1` | First contact is deleted from the list. | Deleted Person: [PERSON DETAILS] |
+ | `delete 1 ec/1` | The first emergency contact of the first contact in the list is deleted. | Deleted emergency contact: [PERSON DETAILS] |
+ | `delete 0` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `delete 2 ec/0` | Error message is shown. | Index is not a non-zero unsigned integer. |
+ | `delete ec/1` | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `delete ec/x` (x > number of emergency contacts) | Error message is shown | The emergency contact index provided is invalid |
+ | `delete x` (x > number of contacts) | Error message is shown | The person index provided is invalid |
+
+
+
+### Adding an emergency contact to a patient
+1. Editing a patient while any number of patients are being shown.
+
+ **Prerequisites:** List all patients using the `list` command. Patients are sorted by the time they were added to MedConnect.
+
+
+ | Test case input | Expected behaviour | Expected message |
+ |------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
+ | `addec 1 ecname/Sarah Lim ecphone/91234567 ecrs/Granddaughter` | The name, phone and email of the first patient in the list is edited to the new values provided as arguments | Added emergency contact: [PERSON DETAILS] |
+ | `addec 1` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `addec ecname/Sarah Lim ecphone/91234567 ecrs/Granddaughter` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `addec 1 ecname/Sarah Lim ecphone/91234567` | Error message is shown. | Invalid command format! [CORRECT COMMAND FORMAT] |
+ | `addec 1 ecname/Sarah Lim ecphone/91234567 ecrs/Neighbor` | Error message is shown | Relationship type should be Parent, Child, Sibling, Spouse, Grandparent or Relative or their gendered variants |
+ | `addec 2 ecname/D%#P! ecphone/91234567 ecrs/Son` | Error message is shown | Names should not be blank and should only contain alphanumeric characters, spaces or the following special characters: - . ( ) @ / ' |
+ | `addec x ecname/Heather ecphone/5137985 ecrs/Sibling` (x > number of contacts) | Error message is shown | Invalid command format! [CORRECT COMMAND FORMAT] |
### Saving data
-1. Dealing with missing/corrupted data files
+ 1. Dealing with missing/corrupted data files
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
1. _{ more test cases … }_
+
+
+
+## **Appendix: Effort**
+
+Developing MedConnect as a brownfield project from the upgrading of AB3 was challenging for us as a team of relatively junior software engineers who did not have much experience in a software engineering project.
+For some of our team members, the only prior software engineering experience we had was our Orbital project.
+
+Initially, we faced many challenges in managing the Git workflow of creating issues, creating branches, merging branches and pull requests.
+This was because the process was new to most of the group and we carefully took the time to learn the proper workflow and avoid merge conflicts.
+
+Another challenge we faced was implementing the autocomplete feature. Since MedConnect was directed to be used by fast typists, we brainstormed the idea of having an autocomplete feature to greatly benefit them.
+However, this idea was quite foreign to all of us and we took a great deal of time in figuring out how to tackle this problem.
+In due time, we managed to figure out a solution as a team and it is now implemented in the current version of MedConnect.
+
+Finally, for the undo and redo feature, we adapted the proposed implementation provided in the developer guide of AB3.
+This greatly reduced the effort required for these features as there are many ways to implement them.
+A more complex solution would be for each command to have its own respective undo and redo implementation.
+However, we followed the proposed implementation of saving the AddressBook in states and having a pointer that points to the current state.
+The pointer would move between states upon execution of the undo and redo commands.
+
+Overall, we faced many challenges as a team that we had to overcome over a short runway.
+We managed to stay afloat and tackle these challenges through constant communication and good teamwork between team members, helping each other out swiftly and decisively.
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index bac5eb36d35..3b931188625 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,22 +1,36 @@
GEM
remote: https://rubygems.org/
specs:
- activesupport (7.0.7.2)
+ activesupport (7.1.5)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
+ connection_pool (>= 2.2.5)
+ drb
i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
minitest (>= 5.1)
+ mutex_m
+ securerandom (>= 0.3)
tzinfo (~> 2.0)
- addressable (2.8.4)
- public_suffix (>= 2.0.2, < 6.0)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ base64 (0.2.0)
+ benchmark (0.3.0)
+ bigdecimal (3.1.8)
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.11.1)
+ coffee-script-source (1.12.2)
colorator (1.1.0)
commonmarker (0.23.10)
- concurrent-ruby (1.2.2)
- dnsruby (1.70.0)
+ concurrent-ruby (1.3.4)
+ connection_pool (2.4.1)
+ csv (3.3.0)
+ dnsruby (1.72.2)
simpleidn (~> 0.2.1)
+ drb (2.2.1)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
@@ -24,25 +38,27 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
eventmachine (1.2.7-x64-mingw32)
- execjs (2.8.1)
- faraday (2.7.5)
- faraday-net_http (>= 2.0, < 3.1)
- ruby2_keywords (>= 0.0.4)
- faraday-net_http (3.0.2)
- ffi (1.15.5)
- ffi (1.15.5-x64-mingw32)
+ execjs (2.10.0)
+ faraday (2.12.0)
+ faraday-net_http (>= 2.0, < 3.4)
+ json
+ logger
+ faraday-net_http (3.3.0)
+ net-http
+ ffi (1.17.0)
+ ffi (1.17.0-x64-mingw32)
forwardable-extended (2.6.0)
- gemoji (3.0.1)
- github-pages (228)
- github-pages-health-check (= 1.17.9)
- jekyll (= 3.9.3)
- jekyll-avatar (= 0.7.0)
- jekyll-coffeescript (= 1.1.1)
- jekyll-commonmark-ghpages (= 0.4.0)
- jekyll-default-layout (= 0.1.4)
- jekyll-feed (= 0.15.1)
+ gemoji (4.1.0)
+ github-pages (232)
+ github-pages-health-check (= 1.18.2)
+ jekyll (= 3.10.0)
+ jekyll-avatar (= 0.8.0)
+ jekyll-coffeescript (= 1.2.2)
+ jekyll-commonmark-ghpages (= 0.5.1)
+ jekyll-default-layout (= 0.1.5)
+ jekyll-feed (= 0.17.0)
jekyll-gist (= 1.5.0)
- jekyll-github-metadata (= 2.13.0)
+ jekyll-github-metadata (= 2.16.1)
jekyll-include-cache (= 0.2.1)
jekyll-mentions (= 1.6.0)
jekyll-optional-front-matter (= 0.3.2)
@@ -69,30 +85,32 @@ GEM
jekyll-theme-tactile (= 0.2.0)
jekyll-theme-time-machine (= 0.2.0)
jekyll-titles-from-headings (= 0.5.3)
- jemoji (= 0.12.0)
- kramdown (= 2.3.2)
+ jemoji (= 0.13.0)
+ kramdown (= 2.4.0)
kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.4)
mercenary (~> 0.3)
minima (= 2.5.1)
- nokogiri (>= 1.13.6, < 2.0)
- rouge (= 3.26.0)
+ nokogiri (>= 1.16.2, < 2.0)
+ rouge (= 3.30.0)
terminal-table (~> 1.4)
- github-pages-health-check (1.17.9)
+ webrick (~> 1.8)
+ github-pages-health-check (1.18.2)
addressable (~> 2.3)
dnsruby (~> 1.60)
- octokit (~> 4.0)
- public_suffix (>= 3.0, < 5.0)
+ octokit (>= 4, < 8)
+ public_suffix (>= 3.0, < 6.0)
typhoeus (~> 1.3)
html-pipeline (2.14.3)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
- i18n (1.14.1)
+ i18n (1.14.6)
concurrent-ruby (~> 1.0)
- jekyll (3.9.3)
+ jekyll (3.10.0)
addressable (~> 2.4)
colorator (~> 1.0)
+ csv (~> 3.0)
em-websocket (~> 0.5)
i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0)
@@ -103,27 +121,28 @@ GEM
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
- jekyll-avatar (0.7.0)
+ webrick (>= 1.0)
+ jekyll-avatar (0.8.0)
jekyll (>= 3.0, < 5.0)
- jekyll-coffeescript (1.1.1)
+ jekyll-coffeescript (1.2.2)
coffee-script (~> 2.2)
- coffee-script-source (~> 1.11.1)
+ coffee-script-source (~> 1.12)
jekyll-commonmark (1.4.0)
commonmarker (~> 0.22)
- jekyll-commonmark-ghpages (0.4.0)
- commonmarker (~> 0.23.7)
- jekyll (~> 3.9.0)
+ jekyll-commonmark-ghpages (0.5.1)
+ commonmarker (>= 0.23.7, < 1.1.0)
+ jekyll (>= 3.9, < 4.0)
jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 5.0)
- jekyll-default-layout (0.1.4)
- jekyll (~> 3.0)
- jekyll-feed (0.15.1)
+ jekyll-default-layout (0.1.5)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-gist (1.5.0)
octokit (~> 4.2)
- jekyll-github-metadata (2.13.0)
+ jekyll-github-metadata (2.16.1)
jekyll (>= 3.4, < 5.0)
- octokit (~> 4.0, != 4.4.0)
+ octokit (>= 4, < 7, != 4.4.0)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-mentions (1.6.0)
@@ -194,42 +213,47 @@ GEM
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
- jemoji (0.12.0)
- gemoji (~> 3.0)
+ jemoji (0.13.0)
+ gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
- kramdown (2.3.2)
+ json (2.7.6)
+ kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
- listen (3.8.0)
+ listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
+ logger (1.6.1)
mercenary (0.3.6)
- mini_portile2 (2.8.6)
+ mini_portile2 (2.8.7)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
- minitest (5.19.0)
- nokogiri (1.16.5)
+ minitest (5.25.1)
+ mutex_m (0.2.0)
+ net-http (0.4.1)
+ uri
+ nokogiri (1.16.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
+ nokogiri (1.16.7-x64-mingw32)
+ racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
- public_suffix (4.0.7)
- racc (1.7.3)
+ public_suffix (5.1.1)
+ racc (1.8.1)
rb-fsevent (0.11.2)
- rb-inotify (0.10.1)
+ rb-inotify (0.11.1)
ffi (~> 1.0)
- rexml (3.3.6)
- strscan
- rouge (3.26.0)
- ruby2_keywords (0.0.5)
+ rexml (3.3.9)
+ rouge (3.30.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
sass (3.7.4)
@@ -240,20 +264,17 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
- simpleidn (0.2.1)
- unf (~> 0.1.4)
- strscan (3.1.0)
+ securerandom (0.3.1)
+ simpleidn (0.2.3)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
- typhoeus (1.4.0)
+ typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- unf (0.1.4)
- unf_ext
- unf_ext (0.0.8.2)
- unf_ext (0.0.8.2-x64-mingw32)
unicode-display_width (1.8.0)
+ uri (0.13.1)
+ wdm (0.1.1)
webrick (1.8.1)
PLATFORMS
@@ -263,7 +284,8 @@ PLATFORMS
DEPENDENCIES
github-pages
jekyll
+ wdm (~> 0.1.0)
webrick
BUNDLED WITH
- 2.1.4
+ 2.2.33
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 84b4ddc4e40..da55a8fd8c3 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,41 +3,123 @@ 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.
+
+
+**:warning: Disclaimer**
+
+The current version of MedConnect is only designed to support the **English language** and for use in a **single country and timezone**.
+
+Using MedConnect with other languages or across multiple countries and timezones may lead to unexpected behaviour.
+
+
+MedConnect is a **desktop app designed for healthcare administrators in elderly care homes for dementia patients**. It consolidates crucial contact information into a single, accessible database, allowing administrative staff to manage patient and doctor contacts efficiently under high-pressure conditions.
+
+MedConnect combines the speed of a Command Line Interface ([CLI](#cli)) with the visual clarity of a Graphical User Interface ([GUI](#gui)), making it ideal for administrators who can type fast and need rapid access to information. MedConnect can get your contact management tasks done faster than traditional [GUI](#gui) apps.
+
+With MedConnect, connecting with on-call doctors, family members, or other essential contacts becomes seamless, helping you respond quickly when every second counts.
+
+
+
+## Table of Contents
+{:.no_toc}
* Table of Contents
{:toc}
---------------------------------------------------------------------------------------------------------------------
+
+
+## How to use this User Guide
+This User Guide is designed to help you understand and use MedConnect effectively. Here are some tips on how to navigate and use this guide:
+1. **[Table of Contents](#table-of-contents)**: At the beginning of the guide, you will find a Table of Contents. Use this to quickly jump to the section you are interested in.
+1. **[Quick Start](#quick-start)**: If you are new to MedConnect, start with the Quick Start section. It provides step-by-step instructions on how to set up and start using the application.
+1. **[Overview of GUI](#overview-of-gui)**: This section provides an overview of the graphical user interface (GUI) of MedConnect. Use this section to familiarize yourself with the different components of the application.
+1. **[Features](#features)**: This section details all the commands available in MedConnect. Each command is explained with its format, parameters, and examples. Use this section to learn how to perform specific tasks.
+1. **[Command Summary](#command-summary)**: At the end of the guide, there is a Command Summary table that provides a quick reference for all commands. Use this table to quickly look up the format of a command.
+1. **[FAQ](#faq)**: The FAQ section addresses common questions and issues. Check this section if you encounter any problems or have questions about using MedConnect.
+1. **[Known Issues](#known-issues)**: This section lists any known issues with the application and their workarounds. Refer to this section if you experience any unexpected behavior.
+1. **[Glossary](#glossary)**: This section explains unfamiliar terms that we use in this User Guide. Check out the glossary if you're unsure what a certain word means.
+1. **Notes and Tips**: Throughout the guide, you will find notes and tips highlighted in different styles. These provide additional information and helpful hints for using MedConnect effectively.
+
+By following these sections, you can quickly find the information you need and make the most out of MedConnect.
+
+[↑ Back to top](#table-of-contents)
-## Quick start
-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).
+## Quick Start
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Ensure you have Java `17` or above installed on your computer.
+ * You can check your Java version by following the instructions [here](https://www.wikihow.com/Check-Your-Java-Version-in-the-Windows-Command-Line).
+ * If you do not have Java `17` or above installed in your computer, you can download Java from [here](https://www.oracle.com/java/technologies/downloads/#java17).
-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)
+2. Download the latest release of the `medconnect.jar` file from [here](https://github.com/AY2425S1-CS2103T-T13-1/tp/releases).
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
+3. Copy the file to the folder you want to use as the _home folder_. The _home folder_ will be where all the data files will be saved.
- * `list` : Lists all contacts.
+4. For *Windows:* Open the home folder and right-click anywhere in the red box, as shown in the image below. Click "Open in Terminal". A terminal window will pop up, then type in the command `java -jar medconnect.jar` to run the application.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+
- * `clear` : Deletes all contacts.
+ For *MacOS:* Right-click home folder. Hover over "Services". Select "New Terminal at folder". A terminal window will pop up, then type in the command `java -jar medconnect.jar` to run the application.
- * `exit` : Exits the app.
+
+
+5. Type the command in the [command box](#command-box) and press `Enter` to execute it. Here are some example commands you can try:
+
+ * `list` : [List](#listing-all-patients--list) all contacts.
+
+ * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 ecname/Charlotte Lim ecphone/81243564 ecrs/Sibling dname/Ronald Lee dphone/99441234 demail/ronaldlee@gmail.com`
+ [Add](#adding-a-patient-add) a contact named `John Doe` to the Address Book with emergency contact `Charlotte Lim` and doctor `Ronald Lee`.
+
+ * `delete 3` : [Delete](#deleting-a-patient--delete) the 3rd contact shown in the current list.
+
+ * `clear` : [Clear](#clearing-all-entries--clear) the contacts list of all contacts.
+
+ * `undo` : [Undo](#undoing-previous-command--undo) previous command.
+
+ * `exit` : [Exit](#exiting-the-program--exit) the app.
1. Refer to the [Features](#features) below for details of each command.
---------------------------------------------------------------------------------------------------------------------
+[↑ Back to top](#table-of-contents)
+
+
+
+## Overview of [GUI](#gui)
+
+![uitutorial](images/uitutorial.png)
+
+* The headers are colour-coded to match the GUI screenshot's boxes above.
+
+Menu Bar
+* Clicking `File` will show the option to exit the application.
+* Clicking `Help` will show `Help F1` which when clicked will link you to this User Guide.
+
+Command Box
+* This is where you will be typing the commands for MedConnect. The full list of commands can be found under [Features](#features).
+
+Result Box
+* This is where MedConnect will give you feedback after you type in a command. It will provide information on whether a command was successful or an invalid input was provided. For more information on the valid command inputs, head to [Features](#features).
+
+Patients List
+* The list of patients will be shown here.
+* You can scroll down the list to see more patients.
+
+Doctor Details
+* Each patient has a doctor assigned to them.
+* The name, phone number and email of the assigned doctor can be easily identified by the blue text colour.
+
+Emergency Contact Details
+* Each patient will have at least one emergency contact listed.
+* The name, phone number and relationship to the patient of each emergency contact is listed here.
+* If a patient has multiple emergency contacts, this box will become scrollable to be able to view more contacts.
+
+[↑ Back to top](#table-of-contents)
+
+
## Features
@@ -48,98 +130,330 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
-* Items in square brackets are optional.
+* 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`.
-* 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.
+* Items with `…` after them can be used multiple times.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. not included), `t/friend`, `t/friend t/family` etc.
+
+* Items without `…` after them should not be inputted multiple times in the command.
-* Parameters can be in any order.
+* Parameters can be in any order unless otherwise stated.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* To avoid being too verbose, some error messages may be simplified for brevity. For a list of valid inputs for each parameter, please refer to the [Glossary](#glossary).
+
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+[↑ Back to top](#table-of-contents)
+
+
+
+### Suggestions and Autocompletion
+
+The Command Box in MedConnect offers **suggestions** and **autocompletion** to assist users while typing commands. These features aim to enhance the command entry experience through reducing the need for memorising command formats and reducing the chance of errors.
+
+### Suggestions
+
+Suggestions provide visible hints about the expected command format as the command is entered.
+
+![suggestion](images/Suggestion.png)
+
+* If an incorrect entry is detected (e.g., type `adding` instead of `add`), the suggestion will be hidden to indicate a problem with the format.
+
+**Examples** (words in brackets "()", are suggested by the system.)
+* Typing `add` will show the expected format for adding a patient: `add (n/NAME p/PHONE e/EMAIL a/ADDRESS ecname/EMERGENCY_CONTACT_NAME ...)`
+* Upon typing the slash commands (e.g. `n/` or `p/`), the suggestion only shows the parameter that needs to be filled or the next command once done. e.g `add n/(NAME)`, `add n/Saajid Shaik (p/PHONE)`
+* Inputs that are prefixes to more than 1 command, will result in full syntax suggestions for those commands to be displayed and separated with a `|`. e.g. `fi(nd KEYWORD MORE_KEYWORDS | finddoc KEYWORD MORE_KEYWORDS)`
+
+
+
+
+
+**:information_source: Note**
+
+While commands can be written in any order, the suggestion feature only follows the default order which puts related parameters together, analogous to how the contact details are displayed in the app.
+
+You may notice that suggestion and autocomplete features almost always work, even without default ordering, but this is currently a feature-in-progress for future enhancement. There are some edge cases that do not work. (e.g. 1st default parameter must always be in order `add n/` to work)
+
+Additionally, suggestions for parameters will never be duplicated unless users manually deviates from autocomplete or suggestion by the system.
+
+
+
+[↑ Back to top](#table-of-contents)
+
+
+### Autocompletion
+
+Autocompletion helps to complete partially typed commands by pressing the `Control` key.
+
+![Autocomplete demonstration](images/autocomplete.gif)
+
+* If an incorrect entry is detected (e.g., type `adding` or `ade` instead of `add`), autocompletion will be disabled to indicate a problem with the format.
+
+**Examples**:
+* Typing `ad` followed by pressing `Control` autocompletes the input to `add`.
+* Typing `add` followed by pressing `Control` autocompletes the input to `add n/`.
+* Typing `add n/Saajid Shaik` followed by pressing `Control` autocompletes the input to `add n/Saajid Shaik p/`.
+
+Since autocomplete can only work on fixed syntaxes, `INDEX`,`KEYWORD` and variable attribute (e.g. `NAME` in commands `n/NAME`) cannot be autocomplete. Should users try to autocomplete, the input text turns red, indicating users to fill in their information as needed.
+
+![red_error](images/red_autocomplete.png)
+
+[↑ Back to top](#table-of-contents)
+
+
+
+
### 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)
+![help message](images/helpWindow.png)
Format: `help`
+[↑ Back to top](#table-of-contents)
+
-### Adding a person: `add`
+### Adding a patient: `add`
-Adds a person to the address book.
+Adds a patient to the address book.
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS ecname/EMERGENCY_CONTACT_NAME ecphone/EMERGENCY_CONTACT_PHONE
+ecrs/EMERGENCY_CONTACT_RELATIONSHIP dname/DOCTOR_NAME dphone/DOCTOR_PHONE demail/DOCTOR_EMAIL [t/TAG]…`
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+A patient can have any number of tags (including 0). Tags can be used to add short descriptions or categories to patients.
-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`
+* You need not enter the prefix 'Dr' when typing `DOCTOR_NAME`. The app automatically adds the prefix `Dr` in front of the `DOCTOR_NAME` entered.
+* Patients with the same `PHONE_NUMBER` will be flagged as duplicates and cannot be added to the address book.
+
+**Examples**
+* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 ecname/Charlotte Lim ecphone/94681352 ecrs/daughter dname/Ronald Lee dphone/99441234 demail/ronaldlee@gmail.com`
+ Adds John Doe as a patient with his daughter Charlotte Lim as his emergency contact and Dr Ronald Lee as his assigned doctor.
+
+* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 ecrs/son ecphone/94873631 ecname/Bob Builder demail/liampayne@gmail.com dphone/91231231 dname/Liam Payne t/criminal`
+ Adds Betsy Crowe as a patient with her son Bob Builder as her emergency contact and Dr Liam Payne as her assigned doctor.
-### Listing all persons : `list`
+[↑ Back to top](#table-of-contents)
-Shows a list of all persons in the address book.
-Format: `list`
+### Listing all patients : `list`
-### Editing a person : `edit`
+Shows a list of every patient in the address book and sets the sort order of the list.
-Edits an existing person in the address book.
+Format: `list [SORT_ORDER]`
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+
+
+Valid inputs for sort order parameter: `timeadded`, `timeadded asc`, `timeadded desc`, `name`, `name asc`, `name desc`
+
+
+
+* If `SORT_ORDER` is not provided, the patients listed will be sorted in the order they were added. The patient who was added the most recently will be at the bottom of the list.
+* `timeadded`, `timeadded asc` and `timeadded desc` sets the patient list to be sorted according to the time they were added to Medconnect.
+ * `timeadded` and `timeadded asc` sorts the patient list from least to most recently added.
+ * `timeadded desc` sorts the patient list from most to least recently added.
+* `name`, `name asc` and `name desc` sets the patient list to be sorted according to their name in alphabetical order.
+ * `name` and `name asc` sorts by the patients' names from uppercase A to Z, followed by lowercase a to z.
+ * `name desc` sorts by the patients' names from lowercase z to a, followed by uppercase Z to A.
+
+**Examples**
+* `list name asc`
+ Sorts the patient list in ascending alphabetical order of their names.
+
+* `list timeadded desc`
+ Sorts the patient list in descending order of time added to MedConnect.
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Editing a patient : `edit`
+
+Edits an existing patient in the address book.
-* 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, …
+Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [ec/EMERGENCY_CONTACT_INDEX] [ecname/EMERGENCY_CONTACT_NAME] [ecphone/EMERGENCY_CONTACT_PHONE] [ecrs/EMERGENCY_CONTACT_RELATIONSHIP] [dname/DOCTOR_NAME] [dphone/DOCTOR_PHONE] [demail/DOCTOR_EMAIL] [t/TAG]…`
+
+* Edits the patient at the specified `INDEX`. Existing values will be updated to the input values.
+* `INDEX` should come before any of the optional fields.
* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* To edit the patient's emergency contact, provide the index of the emergency contact to edit under `ec/EMERGENCY_CONTACT_INDEX` and at least one of the emergency contact fields.
+* When editing tags, all the existing tags of the person will be removed. You will have to re-enter pre-existing tags if you wish to preserve them.
-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.
+
:bulb: **Tip:**
+You can remove all of a person’s existing tags by typing `t/` without specifying any tags after it.
+
+
+**Examples:**
+* `edit 1 p/91234567 e/johndoe@example.com`
+Edits the phone number and email address of the 1st patient to be `91234567` and `johndoe@example.com` respectively.
+
+* `edit 2 n/Betsy Crower t/`
+Edits the name of the 2nd patient to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+* `edit 2 n/Betsy Crower ec/1 ecname/Peter Tan`
+ Edits the name of the 2nd patient to be `Betsy Crower`. Edits the name of the first emergency contact of the 2nd patient to be `Peter Tan`.
-Finds persons whose names contain any of the given keywords.
+[↑ Back to top](#table-of-contents)
+
+
+
+### Locating patients by patient's name: `find`
+
+Finds patients whose names contain any of the given keywords.
Format: `find KEYWORD [MORE_KEYWORDS]`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* **Only the patient's 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`)
+* Names will match if the keyword is found in any part of the name. (e.g. `Ha` will match `Hans`)
+* All patients matching at least one keyword will be shown in the patient list.
+ (e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`)
+* The `find` command filters the list, which gets reset after entering an `add`, `addec`, `edit`, `list`, `undo` or `redo` command.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `find Alex` displays`Alex Yeoh` and `Alexis Tan`
+* `find dav Roy` displays `David Li` and `Roy Balakrishnan`
-### Deleting a person : `delete`
+ ![result for 'find dav roy'](images/findDavRoyResult.png){: width="400"}
-Deletes the specified person from the address book.
+[↑ Back to top](#table-of-contents)
-Format: `delete INDEX`
+
-* 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, …
+### Locating patients by doctor's name: `finddoc`
+
+Finds patients by checking if their doctor's names contain any of the provided keywords.
+
+Format: `finddoc KEYWORD [MORE_KEYWORDS]`
+
+* **Only the doctor's 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`)
+* Names will match if the keyword is found in any part of the doctor's name. (e.g. `Ha` will match `Hans`)
+* Persons matching at least one keyword in their doctor's name will be returned. (e.g. `Hans Bo` will return persons whose doctors are `Hans Gruber`, `Bo Yang`)
+* * The `finddoc` command filters the list, which gets reset after entering an `add`, `addec`, `edit`, `list`, `undo` or `redo` command.
+
+**Examples:**
+* `finddoc John` returns persons with doctors `john` and `John Doe`
+* `finddoc tan ed` returns persons with doctors `Tan Wei Ming`, `Ed Sheeran`
+* `findoc zhou` returns `Irfan Ibrahim` since `Dr Zhou Jie Lun` is his assigned doctor.
+
+ ![result for 'finddoc zhou'](images/finddocZhouResult.png)
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Deleting a patient : `delete`
+
+Deletes the specified patient or emergency contact from the address book.
+
+Format: `delete INDEX [ec/EMERGENCY_CONTACT_INDEX]`
+
+* Deletes the patient at the specified `INDEX` **OR** deletes the emergency contact at the specified `EMERGENCY_CONTACT_INDEX` of the patient at the specified `INDEX`.
+* `INDEX` refers to the index number shown in the displayed person list.
+* The parameters **must** follow the order given above.
+* Each patient must have at least one emergency contact. You cannot delete the final emergency contact in the list.
+
+**Examples:**
+* `delete 2 ec/2` deletes the 2nd emergency contact of the 2nd patient in the address book.
+* `list` followed by `delete 2` deletes the 2nd patient in the address book.
+* `find Betsy` followed by `delete 1` deletes the 1st patient in the results of the `find` command.
+
+[↑ Back to top](#table-of-contents)
+
+
+### Adding an emergency contact : `addec`
+
+Adds an emergency contact to a specified patient in the address book.
+
+Format: `addec INDEX ecname/EMERGENCY_CONTACT_NAME ecphone/EMERGENCY_CONTACT_PHONE ecrs/EMERGENCY_CONTACT_RELATIONSHIP`
+
+* `INDEX` should come before any of the optional fields.
+* A patient cannot have more than one emergency contact with the same phone number.
+
+**Examples**
+* `addec 1 ecname/Shannon Wong ecphone/84651325 ecrs/Daughter`
+Adds a new emergency contact Shannon Wong to the 1st patient in the address book.
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Archiving data files: `archive`
+
+Archives the current address book data to a timestamped data file with an optional description.
+
+Format: `archive [DESCRIPTION]`
+
+**Valid inputs**
+* `DESCRIPTION` must be a valid file name (i.e., it cannot contain any of the following special characters: `\/:*?"<>|`).
+* The archive data file will be saved as a [JSON file](#glossary) in the `[home folder]/data/archive/` folder.
+
+**Examples**
+* `archive` Archives the current address book data to a timestamped data file.
+* `archive before major update` Archives the current address book data to a timestamped data file with the description "before major update".
+
+[↑ Back to top](#table-of-contents)
+
+
+### Listing all archived data files: `listarchives`
+
+Lists the names of all the archived data files in the archive folder.
+
+Format: `listarchives`
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Loading data from an archived data file: `loadarchive`
+
+Loads the data from an archived data file into the address book.
+
+Format: `loadarchive FILE_NAME`
+
+* `FILE_NAME` should be the name of an archived data file in the archive folder. You can view a list of archived data files using the [`listarchives`](#listing-all-archived-data-files-listarchives) command.
+* The data from the archived file will replace the current data in the address book.
+* The data in the archived file will not be deleted.
+
+
:bulb: **Tip:**
+Did you accidentally load an archive and want your old data back? Enter the 'undo' command!
+
+
+**Examples**
+* `loadarchive addressbook-2024-11-06T20-29-05.7609475-example.json` Loads the data from the archived file named `addressbook-2024-11-06T20-29-05.7609475-example.json` into the address book.
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Deleting an archived data file: `deletearchive`
+
+
:exclamation: **Caution:**
+Deleting an archive file is **permanent**. The `undo` command cannot restore a deleted archive file.
+
+
+Deletes the data of an existing archived data file in the archive folder.
+
+Format: `deletearchive FILE_NAME`
+
+* `FILE_NAME` should be the name of an archived data file in the archive folder. You can view a list of archived data files using the [`listarchives`](#listing-all-archived-data-files-listarchives) command.
+
+**Examples**
+* `deletearchive addressbook-2024-11-06T20-29-05.7609475-example.json` Deletes the archived file with the file name `addressbook-2024-11-06T20-29-05.7609475-example.json`.
+
+[↑ Back to top](#table-of-contents)
-Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
### Clearing all entries : `clear`
@@ -147,53 +461,153 @@ Clears all entries from the address book.
Format: `clear`
+[↑ Back to top](#table-of-contents)
+
+
+
+### Undoing previous command : `undo`
+Restores the previous state of the address book after any change, such as an addition, edit, or deletion of a patient.
+
+Format: `undo`
+
+
:exclamation: **Warning:**
+An action cannot be undone once you close the MedConnect application.
+
+
+[↑ Back to top](#table-of-contents)
+
+
+### Redoing previous command : `redo`
+
+Restores the state of the address book **after an undo operation has been executed**, effectively "redoing" the undone changes, such as an addition, edit, or deletion of a patient.
+
+Format: `redo`
+
+
:exclamation: **Warning:**
+An action cannot be redone once you close the MedConnect application.
+
+
+[↑ Back to top](#table-of-contents)
+
+
### Exiting the program : `exit`
Exits the program.
Format: `exit`
+[↑ Back to top](#table-of-contents)
+
+
+
### 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.
+[↑ Back to top](#table-of-contents)
+
+
### 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.
-
: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.
+
+: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.
-### Archiving data files `[coming in v2.0]`
-
-_Details coming soon ..._
+[↑ Back to top](#table-of-contents)
---------------------------------------------------------------------------------------------------------------------
+
## FAQ
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**Q**: How do I 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 MedConnect home folder.
---------------------------------------------------------------------------------------------------------------------
+**Q**: How do I change the MedConnect home folder?
+**A**: The MedConnect home folder is set to the folder where the `medconnect.jar` file is located. If you want to change it, move the `medconnect.jar` file and all the files in the original home folder to the new folder.
+
+[↑ Back to top](#table-of-contents)
## Known issues
-1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
-2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window.
+1. When using multiple screens, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+
+2. If you minimize the Help Window and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window.
+
+3. When typing commands in the CommandBox, inserting a space, e.g. `he lp` in between `he` and `lp`, will cause the suggestion and autocorrection to bug out and display incorrectly.
+
+4. MedConnect supports up to 2,147,483,647 patient contacts. Attempting to add or edit more than this number of contacts may result in unexpected behaviour.
+
+5. Adding a spaces after `/` for command identifiers such as `n/` or `p/` will cause suggestion to repeat. e.g. `add n/ hello world` (spacing between "/" and "h" will cause n/NAME to be suggested again)
+
+[↑ Back to top](#table-of-contents)
+
+
+
+## Glossary
+### Terminology
+
+| Term | Details | Example |
+|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Alphanumeric** | Characters that are either numbers or letters. | 1, 2, 3, A, b, c are alphanumeric characters. |
+| **Command** | Instructions that are given to MedConnect to execute. | [Features](#features) are commands that MedConnect can execute. [`add`](#adding-a-patient-add) is one such command. |
+| **Command Line Interface (CLI)** | A Command Line Interface allows users to interact with an application by typing commands to execute actions. | The command line acts as a CLI in MedConnect. |
+| **Graphical User Interface (GUI)** | A Graphical User Interface allows users to interact with an application through graphics like buttons or icons. | MedConnect acts as a GUI. |
+| **JSON** | JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language. | The data file used by MedConnect is in JSON format. |
+| **Keyword** | The word you want to search for in a `find` or `finddoc` command. | Searching for a patient named Bernice Yu could be done by using keywords `Bern` or `Yu`. |
+| **Parameter** | Information that you are required to provide to the MedConnect command. | `NAME` and `EMAIL` are examples of parameters you have to provide in an [`add`](#adding-a-patient-add) command.
`Paul` and `paul@gmail.com` are possible examples to provide to the respective parameters. |
+
+[↑ Back to top](#table-of-contents)
+
+
+
+### Valid Inputs for Patient parameters
+
+A person is uniquely identified by their `PHONE_NUMBER`. Persons with the same `PHONE_NUMBER` will be flagged as duplicates and cannot be added to the address book.
+
+An emergency contact is considered a duplicate if it all of its fields are the same as another emergency contact. You should not edit an emergency contact to have the same fields as another emergency contact of the same patient to prevent unexpected app behavior. If you edit an emergency contact to have the same name, phone and relationship as another emergency contact of the same patient, this is considered a duplicate emergency contact and will be automatically removed from the list.
+
+| Parameter | Details | Example |
+|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **`name/` NAME** | This parameter accepts alphanumeric characters, the words `s/o`, `d/o`, spaces, and the following characters: `-`, `.`, `(`, `)`, `@`, `/`, `'` | `Connor T'Challa`, `Vika d/o Rajesh`, `Amir Fakri @ Ahmad` and `Buddy (Charles) Baxter` are examples of names you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `name/` paramter.
`Buddy/Charles` is an example of an invalid input to the `name/` parameter. |
+| **`phone/` PHONE_NUMBER** | Phone numbers should only contain numbers and be at least 3 digits long. | `91234567` and `98765432` are examples of phone numbers you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `phone/` parameter. |
+| **`email/` EMAIL** | MedConnect follows the valid email address format detailed [here](https://help.xmatters.com/ondemand/trial/valid_email_format.htm)
Emails should be of the format `local-part@domain` and adhere to the following constraints: 1. `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. 2. This is followed by a `@` and then a domain name for `domain`. 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. | `johndoe@gmail.com` and `janedoe@hotmail.com` are examples of emails you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `email/` parameter. |
+| **`address/` ADDRESS** | Addresses can be any value, but they cannot be blank. | `123, Clementi Rd, 123465` and `Block 123, Jurong West Street 6, #08-111` are examples of addresses you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `address/` parameter. |
+| **`ecname/` EMERGENCY_CONTACT_ NAME** | Refer to `name/` above. | |
+| **`ecphone/` EMERGENCY_CONTACT_ PHONE_NUMBER** | Refer to `phone/` above. | |
+| **`ecrs/` EMERGENCY_CONTACT_ RELATIONSHIP** | This parameter accepts the following valid inputs: `Parent, Mother, Father, Child, Son, Daughter, Sibling, Brother, Sister, Friend, Spouse, Husband, Wife, Partner, Cousin, Relative, Uncle, Aunt, Grandparent, Grandmother, Grandfather, Grandchild, Grandson, Granddaughter`. It is case-insensitive. | `Spouse` and `GRANDcHILD` are examples of relationships you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `ecrs/` parameter. |
+| **`dname/` DOCTOR_NAME** | Refer to `name/` above. | |
+| **`demail/` DOCTOR_EMAIL** | Refer to `email/` above. | |
+| **`dphone/` DOCTOR_PHONE** | Refer to `phone/` above. | |
+| **`t/` TAG** | Tags should only contain alphanumeric characters, spaces, periods `.` or hyphens `-`. | `Mandarin-speaking`, `short-term patient` are examples of tags you can provide in an [`add`](#adding-a-patient-add), [`edit`](#editing-a-patient--edit) or [`addec`](#adding-an-emergency-contact--addec) command `t/` parameter. |
+
+[↑ Back to top](#table-of-contents)
---------------------------------------------------------------------------------------------------------------------
+
## 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 |
+|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS ecname/EMERGENCY_CONTACT_NAME ecphone/EMERGENCY_CONTACT_PHONE ecrs/EMERGENCY_CONTACT_RELATIONSHIP [t/TAG]…`
e.g., `add n/James Ho p/81234567 e/jamesho@example.com a/123, Clementi Rd, 123465` `ecname/Lim Jun Wei ecphone/98765678 ecrs/Brother` `dname/Sam Lim dphone/9987766 demail/samlim@hotmail.com` `t/friend t/colleague` |
+| **Add Emergency Contact** | `addec INDEX ecname/EMERGENCY_CONTACT_NAME ecphone/EMERGENCY_CONTACT_PHONE ecrs/EMERGENCY_CONTACT_RELATIONSHIP` e.g., `addec 1 ecname/Shannon Wong ecphone/84651325 ecrs/Daughter` |
+| **Archive** | `archive [DESCRIPTION]` e.g., `archive before major update` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` e.g., `delete 3` |
+| **Delete Archive File** | `deletearchive FILE_NAME` e.g., `deletearchive addressbook-2024-11-06T20-29-05.7609475-example.json` |
+| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [dname/DOCTOR_NAME] [dphone/DOCTOR_PHONE] [demail/DOCTOR_EMAIL] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com` |
+| **Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake` |
+| **Find Doctor** | `finddoc KEYWORD [MORE_KEYWORDS]` e.g., `find Tan Sheeran` |
+| **Help** | `help` |
+| **List** | `list [SORT_ORDER]` e.g., `list timeadded desc` |
+| **List Archive Files** | `listarchives` |
+| **Load Archive File** | `loadarchive FILE_NAME` e.g., `loadarchive addressbook-2024-11-06T20-29-05.7609475-example.json` |
+| **Redo** | `redo` |
+| **Undo** | `undo` |
+
+[↑ Back to top](#table-of-contents)
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..39cb644c7e2 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "MedConnect"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "ay2425s1-cs2103t-t13-1/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..2d2647b64c0 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "MedConnect";
font-size: 32px;
}
}
diff --git a/docs/diagrams/ArchiveSequenceDiagram.puml b/docs/diagrams/ArchiveSequenceDiagram.puml
new file mode 100644
index 00000000000..ff81f0a75b5
--- /dev/null
+++ b/docs/diagrams/ArchiveSequenceDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+actor User
+participant ArchiveCommand
+participant ModelManager
+participant FileUtil
+
+User -> ArchiveCommand: execute(Model)
+activate ArchiveCommand
+
+ArchiveCommand -> ModelManager: archiveAddressBook(Filename)
+activate ModelManager
+
+ModelManager -> FileUtil: Create archive directory if not exists
+ModelManager -> FileUtil: Copy current address book to archive directory
+ModelManager --> ArchiveCommand:
+deactivate ModelManager
+
+ArchiveCommand --> User: CommandResult(MESSAGE_SUCCESS)
+deactivate ArchiveCommand
+@enduml
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 598474a5c82..4cbfbc6ab07 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -14,8 +14,10 @@ UniquePersonList -right-> Person
Person -up-> "*" Tag
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
+Person *--> "1"Name
+Person *--> "1"Phone
+Person *--> "1"Email
+Person *--> "1"Address
+Person --> "1..*"EmergencyContact
+Person --> "1"Doctor
@enduml
diff --git a/docs/diagrams/DoctorClassDiagram.puml b/docs/diagrams/DoctorClassDiagram.puml
new file mode 100644
index 00000000000..b2d43fd4dee
--- /dev/null
+++ b/docs/diagrams/DoctorClassDiagram.puml
@@ -0,0 +1,11 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Doctor *--> "1"DoctorName
+Doctor *--> "1"Phone
+Doctor *--> "1"Email
+
+@enduml
diff --git a/docs/diagrams/EmergencyContactClassDiagram.puml b/docs/diagrams/EmergencyContactClassDiagram.puml
new file mode 100644
index 00000000000..13003c29dca
--- /dev/null
+++ b/docs/diagrams/EmergencyContactClassDiagram.puml
@@ -0,0 +1,11 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+EmergencyContact *--> "1"Name
+EmergencyContact *--> "1"Phone
+EmergencyContact *--> "1"Relationship
+
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml
new file mode 100644
index 00000000000..ca8158fe533
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram.puml
@@ -0,0 +1,26 @@
+@startuml FindSequenceDiagram
+
+actor User
+participant FindCommand
+participant ModelManager
+
+User -> FindCommand: execute(Model)
+activate FindCommand
+
+FindCommand -> ModelManager: updateFilteredPersonList(Predicate)
+activate ModelManager
+
+ModelManager -> ModelManager: updateFilteredPersonList(Predicate)
+
+ModelManager -> ModelManager: setPredicate(Predicate)
+ModelManager --> FindCommand:
+
+FindCommand -> ModelManager: getFilteredPersonList()
+ModelManager -> ModelManager: size()
+ModelManager --> FindCommand: ObservableList
+deactivate ModelManager
+
+FindCommand --> User: CommandResult
+deactivate FindCommand
+
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..01fb95715e6 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -19,6 +19,8 @@ Class Email
Class Name
Class Phone
Class Tag
+Class EmergencyContact
+Class Doctor
Class I #FFFFFF
}
@@ -37,11 +39,13 @@ UserPrefs .up.|> ReadOnlyUserPrefs
AddressBook *--> "1" UniquePersonList
UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
+Person *--> "1" Name
+Person *--> "1" Phone
+Person *--> "1" Email
+Person *--> "1" Address
+Person *--> "1..*" EmergencyContact
Person *--> "*" Tag
+Person *--> "1" Doctor
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
diff --git a/docs/diagrams/PersonClassDiagram.puml b/docs/diagrams/PersonClassDiagram.puml
new file mode 100644
index 00000000000..05ded23e1c4
--- /dev/null
+++ b/docs/diagrams/PersonClassDiagram.puml
@@ -0,0 +1,16 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+
+Person *--> "1"Name
+Person *--> "1"Phone
+Person *--> "1"Email
+Person *--> "1..*"EmergencyContact
+Person *--> "1"Doctor
+Person *--> "1"Address
+Person *--> "*" Tag
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..2e8e582bdf8 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -20,6 +20,8 @@ Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
Class JsonAdaptedTag
+Class JsonAdaptedDoctor
+Class JsonAdaptedEmergencyContact
}
}
@@ -39,5 +41,7 @@ JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonAdaptedPerson --> "*" JsonAdaptedEmergencyContact
+JsonAdaptedPerson --> "1" JsonAdaptedDoctor
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..497c0da171a 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -1,4 +1,4 @@
-@startuml
+ @startuml
!include style.puml
skinparam arrowThickness 1.1
skinparam arrowColor UI_COLOR_T4
@@ -15,6 +15,8 @@ Class PersonListPanel
Class PersonCard
Class StatusBarFooter
Class CommandBox
+Class EmergencyContactListPanel
+Class EmergencyContactCard
}
package Model <> {
@@ -43,14 +45,19 @@ MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+PersonCard -left-|> UiPart
+EmergencyContactListPanel --|> UiPart
+EmergencyContactCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
PersonCard ..> Model
+PersonCard *-down-> EmergencyContactListPanel
+EmergencyContactListPanel -down-> "1..*" EmergencyContactCard
UiManager -right-> Logic
MainWindow -left-> Logic
+'Unsure how to add EmergencyContact UI classes to this segment'
PersonListPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 5a41e9e1651..b746daf1785 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -12,6 +12,7 @@ package States <> {
class State3 as "ab2:AddressBook"
}
+
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index ad32fce1b0b..acc1aea0eb7 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -12,6 +12,7 @@ package States <> {
class State3 as "ab2:AddressBook"
}
+
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
index 9187a690036..52785e39aa9 100644
--- a/docs/diagrams/UndoRedoState3.puml
+++ b/docs/diagrams/UndoRedoState3.puml
@@ -12,6 +12,7 @@ package States <> {
class State3 as "ab2:AddressBook"
}
+
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
index 2bc631ffcd0..a9b2b93907b 100644
--- a/docs/diagrams/UndoRedoState4.puml
+++ b/docs/diagrams/UndoRedoState4.puml
@@ -12,6 +12,7 @@ package States <> {
class State3 as "ab2:AddressBook"
}
+
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
index e77b04104aa..a76733d19ba 100644
--- a/docs/diagrams/UndoRedoState5.puml
+++ b/docs/diagrams/UndoRedoState5.puml
@@ -12,6 +12,7 @@ package States <> {
class State3 as "ab3:AddressBook"
}
+
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/images/ArchiveSequenceDiagram.png b/docs/images/ArchiveSequenceDiagram.png
new file mode 100644
index 00000000000..7a317bfe08f
Binary files /dev/null and b/docs/images/ArchiveSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 02a42e35e76..6b14adb38cd 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png
new file mode 100644
index 00000000000..39c2e35fcf4
Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..d4322d26aef 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/PersonClassDiagram.png b/docs/images/PersonClassDiagram.png
new file mode 100644
index 00000000000..1fcc8253290
Binary files /dev/null and b/docs/images/PersonClassDiagram.png differ
diff --git a/docs/images/Quickstart-new-terminal-MacOS.png b/docs/images/Quickstart-new-terminal-MacOS.png
new file mode 100644
index 00000000000..a76cf7c0fb5
Binary files /dev/null and b/docs/images/Quickstart-new-terminal-MacOS.png differ
diff --git a/docs/images/Quickstart-new-terminal.png b/docs/images/Quickstart-new-terminal.png
new file mode 100644
index 00000000000..e2c36f0d353
Binary files /dev/null and b/docs/images/Quickstart-new-terminal.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..13819f6a7e3 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Suggestion.png b/docs/images/Suggestion.png
new file mode 100644
index 00000000000..9cef92177d6
Binary files /dev/null and b/docs/images/Suggestion.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..f1f41ac4e7a 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..dee6dd1df6f 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/aldentantan.png b/docs/images/aldentantan.png
new file mode 100644
index 00000000000..61ee03fc725
Binary files /dev/null and b/docs/images/aldentantan.png differ
diff --git a/docs/images/autocomplete.gif b/docs/images/autocomplete.gif
new file mode 100644
index 00000000000..b575daf365c
Binary files /dev/null and b/docs/images/autocomplete.gif 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/findDavRoyResult.png b/docs/images/findDavRoyResult.png
new file mode 100644
index 00000000000..6c2821567ef
Binary files /dev/null and b/docs/images/findDavRoyResult.png differ
diff --git a/docs/images/finddocZhouResult.png b/docs/images/finddocZhouResult.png
new file mode 100644
index 00000000000..3b315415b9a
Binary files /dev/null and b/docs/images/finddocZhouResult.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/helpWindow.png b/docs/images/helpWindow.png
new file mode 100644
index 00000000000..65401169b67
Binary files /dev/null and b/docs/images/helpWindow.png differ
diff --git a/docs/images/johnwz123.png b/docs/images/johnwz123.png
new file mode 100644
index 00000000000..a4a20ee7696
Binary files /dev/null and b/docs/images/johnwz123.png differ
diff --git a/docs/images/kellywsq03.png b/docs/images/kellywsq03.png
new file mode 100644
index 00000000000..446278bf65f
Binary files /dev/null and b/docs/images/kellywsq03.png differ
diff --git a/docs/images/red_autocomplete.png b/docs/images/red_autocomplete.png
new file mode 100644
index 00000000000..cd212f3c199
Binary files /dev/null and b/docs/images/red_autocomplete.png differ
diff --git a/docs/images/saajidshaik02.png b/docs/images/saajidshaik02.png
new file mode 100644
index 00000000000..5a59b39ee2b
Binary files /dev/null and b/docs/images/saajidshaik02.png differ
diff --git a/docs/images/sampleUiImage.png b/docs/images/sampleUiImage.png
new file mode 100644
index 00000000000..a47da8a747f
Binary files /dev/null and b/docs/images/sampleUiImage.png differ
diff --git a/docs/images/similarprefix.png b/docs/images/similarprefix.png
new file mode 100644
index 00000000000..da0e85ad045
Binary files /dev/null and b/docs/images/similarprefix.png differ
diff --git a/docs/images/uitutorial.png b/docs/images/uitutorial.png
new file mode 100644
index 00000000000..00b86d6b0c5
Binary files /dev/null and b/docs/images/uitutorial.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..6bd7abcad38 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,25 @@
---
layout: page
-title: AddressBook Level-3
+title: MedConnect
---
-[![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/AY2425S1-CS2103T-T13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-T13-1/tp/actions)
+[![codecov](https://codecov.io/gh/AY2425S1-CS2103T-T13-1/tp/graph/badge.svg)](https://codecov.io/github/AY2425S1-CS2103T-T13-1/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**MedConnect is a desktop application for healthcare administrators in old folks homes for dementia patients to consolidate contacts of patients and related information into a single database.**
-* 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.
+It enables speedy lookups and updates, ensuring that administrators can quickly connect with the right people, from patients' doctors to their families, when every second counts.
+
+While it has a GUI (Graphical User Interface), most of the user interactions happen using a CLI (Command Line Interface).
+
+* If you are interested in using MedConnect, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing MedConnect, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
+This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+
* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 678ddc8c218..fcc9a8c6468 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -12,6 +12,7 @@
import seedu.address.commons.core.Version;
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.commons.util.ConfigUtil;
+import seedu.address.commons.util.FileUtil;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.Logic;
import seedu.address.logic.LogicManager;
@@ -36,7 +37,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 6, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -61,6 +62,9 @@ public void init() throws Exception {
storage = new StorageManager(addressBookStorage, userPrefsStorage);
model = initModelManager(storage, userPrefs);
+ if (!FileUtil.isFileExists(model.getAddressBookFilePath())) {
+ storage.saveAddressBook(model.getAddressBook());
+ }
logic = new LogicManager(model, storage);
@@ -171,6 +175,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
logger.info("Starting AddressBook " + MainApp.VERSION);
+ primaryStage.setMaximized(true);
ui.start(primaryStage);
}
diff --git a/src/main/java/seedu/address/commons/core/filename/Filename.java b/src/main/java/seedu/address/commons/core/filename/Filename.java
new file mode 100644
index 00000000000..e9a473e9465
--- /dev/null
+++ b/src/main/java/seedu/address/commons/core/filename/Filename.java
@@ -0,0 +1,65 @@
+package seedu.address.commons.core.filename;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ *
+ */
+public class Filename {
+ public static final String MESSAGE_CONSTRAINTS_BLANK = "Filename should not be blank";
+ public static final String MESSAGE_CONSTRAINTS = "Invalid filename. Filenames should not contain any of the "
+ + "following characters: < > : \" / \\ | ? *";
+
+ /* Regex pattern for valid Windows filenames:
+ * - Must not contain any of the following characters: < > : " / \ | ? *
+ */
+ private static final String WINDOWS_VALIDATION_REGEX = "^[^<>:\"/\\\\|?*]*$";
+
+
+ /* Regex pattern for valid Linux and MacOS filenames:
+ * - Must not contain a forward slash (/)
+ */
+ private static final String LINUX_MAC_VALIDATION_REGEX = "^[^/]*$";
+
+ private String filename;
+
+ /**
+ * Constructs a {@code Filename}.
+ *
+ * @param filename A valid filename.
+ */
+ public Filename(String filename) {
+ requireNonNull(filename);
+ checkArgument(isValidFilename(filename), MESSAGE_CONSTRAINTS);
+ this.filename = filename;
+ }
+
+ /**
+ * Returns true if a given string is a valid filename.
+ *
+ * @param test String to test.
+ */
+ public static boolean isValidFilename(String test) {
+ return test.matches(WINDOWS_VALIDATION_REGEX) && test.matches(LINUX_MAC_VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Filename otherFilename)) {
+ return false;
+ }
+
+ return filename.equals(otherFilename.filename);
+ }
+
+ @Override
+ public String toString() {
+ return filename;
+ }
+}
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java
index dd170d8b68d..a8b1e2e47e2 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/seedu/address/commons/core/index/Index.java
@@ -25,14 +25,6 @@ private Index(int zeroBasedIndex) {
this.zeroBasedIndex = zeroBasedIndex;
}
- public int getZeroBased() {
- return zeroBasedIndex;
- }
-
- public int getOneBased() {
- return zeroBasedIndex + 1;
- }
-
/**
* Creates a new {@code Index} using a zero-based index.
*/
@@ -47,6 +39,14 @@ public static Index fromOneBased(int oneBasedIndex) {
return new Index(oneBasedIndex - 1);
}
+ public int getZeroBased() {
+ return zeroBasedIndex;
+ }
+
+ public int getOneBased() {
+ return zeroBasedIndex + 1;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -66,4 +66,5 @@ public boolean equals(Object other) {
public String toString() {
return new ToStringBuilder(this).add("zeroBasedIndex", zeroBasedIndex).toString();
}
+
}
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..c5ed643f89e 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -14,11 +14,10 @@ public class StringUtil {
/**
* Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
+ * Ignores case and returns a match as long as the word matches a substring in the sentence.
* examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
- * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
*
* @param sentence cannot be null
* @param word cannot be null, cannot be empty, must be a single word
@@ -35,7 +34,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ .anyMatch(wordInSentence -> wordInSentence.toLowerCase().contains(preppedWord.toLowerCase()));
}
/**
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..f026a156b6d 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -5,6 +5,7 @@
import java.util.stream.Stream;
import seedu.address.logic.parser.Prefix;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Person;
/**
@@ -13,8 +14,12 @@
public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
- public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
+ public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n\n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_EMERGENCY_CONTACT_DISPLAYED_INDEX =
+ "The emergency contact index provided is invalid";
+ public static final String MESSAGE_LAST_EMERGENCY_CONTACT_INDEX =
+ "The person must have at least one emergency contact. You cannot delete the last emergency contact.";
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): ";
@@ -31,19 +36,37 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
return MESSAGE_DUPLICATE_FIELDS + String.join(" ", duplicateFields);
}
+ /**
+ * Formats the {@code person} for display to the user.
+ */
+ public static String formatEmergencyContact(EmergencyContact emergencyContact) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(emergencyContact.getName())
+ .append("; Phone: ")
+ .append(emergencyContact.getPhone())
+ .append("; Relationship: ")
+ .append(emergencyContact.getRelationship());
+ return builder.toString();
+ }
+
/**
* Formats the {@code person} for display to the user.
*/
public static String format(Person person) {
final StringBuilder builder = new StringBuilder();
builder.append(person.getName())
- .append("; Phone: ")
+ .append("\nPhone: ")
.append(person.getPhone())
- .append("; Email: ")
+ .append("\nEmail: ")
.append(person.getEmail())
- .append("; Address: ")
+ .append("\nAddress: ")
.append(person.getAddress())
- .append("; Tags: ");
+ .append("\nEmergency Contact(s): ");
+ person.getEmergencyContacts().forEach(x -> {
+ builder.append(x);
+ builder.append("\n");
+ });
+ builder.append("\nTags: ");
person.getTags().forEach(builder::append);
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..3e10e4eed58 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -2,7 +2,13 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
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;
@@ -20,23 +26,36 @@ public class AddCommand extends Command {
public static final String COMMAND_WORD = "add";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. \n\n"
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + PREFIX_EMERGENCY_CONTACT_NAME + "EMERGENCY CONTACT NAME "
+ + PREFIX_EMERGENCY_CONTACT_PHONE + "EMERGENCY CONTACT PHONE "
+ + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "EMERGENCY CONTACT RELATIONSHIP "
+ + PREFIX_DOC_NAME + "DOCTOR NAME "
+ + PREFIX_DOC_PHONE + "DOCTOR PHONE "
+ + PREFIX_DOC_EMAIL + "DOCTOR EMAIL "
+ + "[" + PREFIX_TAG + "TAG]...\n\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_EMERGENCY_CONTACT_NAME + "Beatrice Bean "
+ + PREFIX_EMERGENCY_CONTACT_PHONE + "91324856 "
+ + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "Son "
+ + PREFIX_DOC_NAME + "Tan Wei Ming "
+ + PREFIX_DOC_PHONE + "62345678 "
+ + PREFIX_DOC_EMAIL + "tanweiming@gmail.com "
+ + PREFIX_TAG + "needs mobility support "
+ + PREFIX_TAG + "short-term stay";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_DUPLICATE_PERSON =
+ "A person with the same phone number already exists in the address book";
private final Person toAdd;
diff --git a/src/main/java/seedu/address/logic/commands/AddEmergencyContactCommand.java b/src/main/java/seedu/address/logic/commands/AddEmergencyContactCommand.java
new file mode 100644
index 00000000000..3b117b41660
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddEmergencyContactCommand.java
@@ -0,0 +1,125 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+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.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Adds an emergency contact to an existing person in the address book.
+ */
+public class AddEmergencyContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "addec";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a new emergency contact to the person "
+ + "identified by the index number used in the displayed person list.\n\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_EMERGENCY_CONTACT_NAME + "EMERGENCY CONTACT NAME "
+ + PREFIX_EMERGENCY_CONTACT_PHONE + "EMERGENCY CONTACT PHONE "
+ + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "EMERGENCY CONTACT RELATIONSHIP\n\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_EMERGENCY_CONTACT_NAME + "Sarah Lim "
+ + PREFIX_EMERGENCY_CONTACT_PHONE + "91234567 "
+ + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "Granddaughter";
+
+ public static final String MESSAGE_SUCCESS = "Added emergency contact: %1$s";
+ public static final String MESSAGE_DUPLICATE_EMERGENCY_CONTACT = "This person is already an emergency contact.";
+
+ private final Index index;
+ private final EmergencyContact emergencyContactToAdd;
+
+ /**
+ * @param index of the person in the filtered person list to add
+ * @param emergencyContactToAdd to
+ */
+ public AddEmergencyContactCommand(Index index, EmergencyContact emergencyContactToAdd) {
+ requireNonNull(index);
+ this.index = index;
+ this.emergencyContactToAdd = emergencyContactToAdd;
+ }
+
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToAddEmergencyContactTo}
+ * with an added {@code emergencyContactToAdd}.
+ */
+ public static Person createEditedPerson(Person personToEdit, EmergencyContact emergencyContactToAdd) {
+ assert personToEdit != null;
+
+ Set personToEditEmergencyContacts = personToEdit.getEmergencyContacts();
+ Set updatedEmergencyContacts = new LinkedHashSet<>(personToEditEmergencyContacts);
+ updatedEmergencyContacts.add(emergencyContactToAdd);
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Doctor doctor = personToEdit.getDoctor();
+ Set tags = personToEdit.getTags();
+
+ return new Person(name, phone, email, address, updatedEmergencyContacts, doctor, tags);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToAddEmergencyContactTo = lastShownList.get(index.getZeroBased());
+
+ if (personToAddEmergencyContactTo.hasEmergencyContact(emergencyContactToAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMERGENCY_CONTACT);
+ }
+
+ Person editedPerson = createEditedPerson(personToAddEmergencyContactTo, emergencyContactToAdd);
+
+ model.setPerson(personToAddEmergencyContactTo, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(editedPerson)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddEmergencyContactCommand)) {
+ return false;
+ }
+
+ AddEmergencyContactCommand otherAddEmergencyContactCommand = (AddEmergencyContactCommand) other;
+ return emergencyContactToAdd.equals(otherAddEmergencyContactCommand.emergencyContactToAdd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toAdd", emergencyContactToAdd)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ArchiveCommand.java b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java
new file mode 100644
index 00000000000..2bf976a7f4a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Archives the address book.
+ */
+public class ArchiveCommand extends Command {
+
+ public static final String COMMAND_WORD = "archive";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Archives the address book.\n\n"
+ + "Example: " + COMMAND_WORD + " " + "1st Quarter 2021";
+ public static final String MESSAGE_SUCCESS = "Address book has been archived successfully!";
+ public static final String MESSAGE_FAILURE = "Address book failed to be archived. Please try again later.";
+
+ private final Filename filename;
+
+ public ArchiveCommand() {
+ this.filename = new Filename("");
+ }
+
+ public ArchiveCommand(Filename filename) {
+ this.filename = filename;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ try {
+ model.archiveAddressBook(filename);
+ } catch (IOException e) {
+ throw new CommandException(MESSAGE_FAILURE);
+ }
+
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ArchiveCommand)) {
+ return false;
+ }
+
+ ArchiveCommand otherArchiveCommand = (ArchiveCommand) other;
+ return filename.equals(otherArchiveCommand.filename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteArchiveCommand.java b/src/main/java/seedu/address/logic/commands/DeleteArchiveCommand.java
new file mode 100644
index 00000000000..963777d9320
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteArchiveCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Deletes an archive file.
+ */
+public class DeleteArchiveCommand extends Command {
+ public static final String COMMAND_WORD = "deletearchive";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes an archive file.\n\n"
+ + "Parameters: FILENAME\n\n"
+ + "Example: " + COMMAND_WORD + " addressbook-2024-11-06T20-29-05.7609475-example.json";
+
+ public static final String MESSAGE_SUCCESS = "Deleted archive file: %1$s";
+ public static final String MESSAGE_NOT_FOUND = "Archive file not found: %1$s";
+ public static final String MESSAGE_FAILURE = "Failed to delete archive file: %1$s";
+
+ private static final Logger logger = LogsCenter.getLogger(DeleteArchiveCommand.class);
+
+ private final Filename archiveFilename;
+
+ public DeleteArchiveCommand(Filename archiveFilename) {
+ this.archiveFilename = archiveFilename;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Path archiveFile = Paths.get(model.getArchiveDirectoryPath().toString(), archiveFilename.toString());
+ if (!FileUtil.isFileExists(archiveFile)) {
+ logger.info("Archive file not found: " + archiveFilename);
+ throw new CommandException(String.format(MESSAGE_NOT_FOUND, archiveFilename));
+ }
+
+ try {
+ Files.deleteIfExists(archiveFile);
+ } catch (IOException e) {
+ logger.severe("Failed to delete archive file: " + e.getMessage());
+ throw new CommandException(String.format(MESSAGE_FAILURE, archiveFilename));
+ }
+
+ logger.info("Deleted archive file: " + archiveFilename);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, archiveFilename));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteArchiveCommand)) {
+ return false;
+ }
+
+ DeleteArchiveCommand otherDeleteCommand = (DeleteArchiveCommand) other;
+ return archiveFilename.equals(otherDeleteCommand.archiveFilename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..ba18447dfa8 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,15 +1,26 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
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.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
/**
* Deletes a person identified using it's displayed index from the address book.
@@ -19,16 +30,70 @@ public class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
+ + ": Deletes the person by the index number used in the displayed person list or emergency contact "
+ + "identified by the index number used in the displayed emergency contact list.\n\n"
+ + "Parameters: INDEX (must be a positive integer) [" + PREFIX_EMERGENCY_CONTACT_TO_EDIT
+ + "EMERGENCY CONTACT INDEX (must be a positive integer)]\n\n"
+ + "Example: " + COMMAND_WORD + " 1 " + PREFIX_EMERGENCY_CONTACT_TO_EDIT + "1";
public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_EMERGENCY_CONTACT_SUCCESS = "Deleted Emergency Contact: %1$s";
+
private final Index targetIndex;
+ private final DeleteCommandDescriptor deleteCommandDescriptor;
- public DeleteCommand(Index targetIndex) {
+ /**
+ * @param targetIndex of the person in the filtered person list to delete
+ * @param deleteCommandDescriptor carries emergency contact index, if present
+ */
+ public DeleteCommand(Index targetIndex, DeleteCommandDescriptor deleteCommandDescriptor) {
this.targetIndex = targetIndex;
+ this.deleteCommandDescriptor = deleteCommandDescriptor;
+ }
+
+ /**
+ * Helper function which returns a new person with the updatedEmergencyContacts to abide by
+ * immutability of the Person class
+ * @param personToEdit person in the filtered person list to delete
+ * @param updatedEmergencyContacts updated emergency contacts list
+ */
+ public static Person createEditedPerson(Person personToEdit, Set updatedEmergencyContacts) {
+ assert personToEdit != null;
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Doctor doctor = personToEdit.getDoctor();
+ Set tagSet = personToEdit.getTags();
+
+ return new Person(name, phone, email, address, updatedEmergencyContacts, doctor, tagSet);
+ }
+
+ private CommandResult executeDeleteEmergencyContact(Index emergencyContactIndex,
+ Person personToDelete,
+ Model model) throws CommandException {
+ if (emergencyContactIndex.getOneBased() > personToDelete.getEmergencyContacts().size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EMERGENCY_CONTACT_DISPLAYED_INDEX);
+ }
+
+ if (personToDelete.hasOnlyOneEmergencyContact()) {
+ throw new CommandException(Messages.MESSAGE_LAST_EMERGENCY_CONTACT_INDEX);
+ }
+
+ EmergencyContact deletedEmergencyContact =
+ personToDelete.getEmergencyContact(emergencyContactIndex);
+
+ Set updatedEmergencyContacts =
+ personToDelete.removeEmergencyContact(deletedEmergencyContact);
+
+ Person updatedPerson = createEditedPerson(personToDelete, updatedEmergencyContacts);
+
+ // Refreshes model
+ model.setPerson(personToDelete, updatedPerson);
+ return new CommandResult(String.format(MESSAGE_DELETE_EMERGENCY_CONTACT_SUCCESS,
+ Messages.formatEmergencyContact(deletedEmergencyContact)));
}
@Override
@@ -41,6 +106,12 @@ public CommandResult execute(Model model) throws CommandException {
}
Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ Optional emergencyContactIndex = deleteCommandDescriptor.getEmergencyContactIndex();
+ if (emergencyContactIndex.isPresent()) {
+ return executeDeleteEmergencyContact(emergencyContactIndex.get(), personToDelete, model);
+ }
+
model.deletePerson(personToDelete);
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
}
@@ -66,4 +137,53 @@ public String toString() {
.add("targetIndex", targetIndex)
.toString();
}
+
+ /**
+ * Stores the details to edit the person with. Each non-empty field value will
+ * replace the
+ * corresponding field value of the person.
+ */
+ public static class DeleteCommandDescriptor {
+ private Index emergencyContactIndex;
+
+ public DeleteCommandDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public DeleteCommandDescriptor(DeleteCommandDescriptor toCopy) {
+ setEmergencyContactIndex(toCopy.emergencyContactIndex);
+ }
+
+ public Optional getEmergencyContactIndex() {
+ return Optional.ofNullable(emergencyContactIndex);
+ }
+
+ public void setEmergencyContactIndex(Index emergencyContactIndex) {
+ this.emergencyContactIndex = emergencyContactIndex;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteCommandDescriptor)) {
+ return false;
+ }
+
+ DeleteCommandDescriptor otherDeleteCommandDescriptor = (DeleteCommandDescriptor) other;
+ return Objects.equals(emergencyContactIndex, otherDeleteCommandDescriptor.emergencyContactIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("emergency contact index", emergencyContactIndex)
+ .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..8bc877a6bfb 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,8 +1,16 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.commands.AddEmergencyContactCommand.MESSAGE_DUPLICATE_EMERGENCY_CONTACT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
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;
@@ -10,6 +18,7 @@
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -22,10 +31,14 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
/**
@@ -37,36 +50,135 @@ public class EditCommand extends Command {
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
+ "by the index number used in the displayed person list. "
- + "Existing values will be overwritten by the input values.\n"
+ + "Existing values will be overwritten by the input values.\n\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_EMERGENCY_CONTACT_TO_EDIT + "EMERGENCY_CONTACT_INDEX] "
+ + "[" + PREFIX_EMERGENCY_CONTACT_NAME + "EMERGENCY CONTACT NAME] "
+ + "[" + PREFIX_EMERGENCY_CONTACT_PHONE + "EMERGENCY CONTACT PHONE] "
+ + "[" + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "EMERGENCY CONTACT RELATIONSHIP] "
+ + "[" + PREFIX_DOC_NAME + "DOCTOR NAME] "
+ + "[" + PREFIX_DOC_PHONE + "DOCTOR PHONE] "
+ + "[" + PREFIX_DOC_EMAIL + "DOCTOR EMAIL] "
+ + "[" + PREFIX_TAG + "TAG]...\n\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
+ + PREFIX_EMAIL + "johndoe@example.com "
+ + PREFIX_EMERGENCY_CONTACT_TO_EDIT + "1 "
+ + PREFIX_EMERGENCY_CONTACT_NAME + "John Kentucky";
public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_PERSON =
+ "A person with the same phone number already exists in the address book.";
+ public static final String MESSAGE_EMERGENCY_CONTACT_NOT_EDITED = "At least one emergency contact field to edit "
+ + "must be provided.";
+ public static final String MESSAGE_EMERGENCY_CONTACT_FIELDS_INVALID = "At least one emergency contact index to "
+ + "edit must be provided.";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
/**
- * @param index of the person in the filtered person list to edit
+ * @param index of the person in the filtered person list to edit
* @param editPersonDescriptor details to edit the person with
*/
public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
requireNonNull(index);
requireNonNull(editPersonDescriptor);
-
this.index = index;
this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
}
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * edited with {@code editPersonDescriptor}.
+ */
+ private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor)
+ throws CommandException {
+ assert personToEdit != null;
+
+ Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
+ Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
+ Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
+ Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Set updatedEmergencyContacts = new LinkedHashSet<>();
+
+ if (editPersonDescriptor.getIndexOfEmergencyContactToEdit().isPresent()) {
+ Index index = editPersonDescriptor.getIndexOfEmergencyContactToEdit().get();
+ Set personEmergencyContacts = personToEdit.getEmergencyContacts();
+ if (index.getZeroBased() >= personEmergencyContacts.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EMERGENCY_CONTACT_DISPLAYED_INDEX);
+ }
+
+ EmergencyContact emergencyContactToUpdate = personToEdit.getEmergencyContact(index);
+ EmergencyContact updatedEmergencyContact =
+ createEditedEmergencyContact(emergencyContactToUpdate, editPersonDescriptor);
+
+ if (personToEdit.hasEmergencyContact(updatedEmergencyContact)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMERGENCY_CONTACT);
+ }
+ updatedEmergencyContacts =
+ updateEmergencyContacts(personEmergencyContacts, updatedEmergencyContact, index);
+ } else {
+ updatedEmergencyContacts = personToEdit.getEmergencyContacts();
+ }
+
+ Doctor updatedDoctor = createEditedDoctor(personToEdit.getDoctor(), editPersonDescriptor);
+ Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress,
+ updatedEmergencyContacts, updatedDoctor, updatedTags);
+ }
+
+ private static Set updateEmergencyContacts(Set personEmergencyContacts,
+ EmergencyContact updatedEmergencyContact,
+ Index index) {
+ assert !personEmergencyContacts.isEmpty();
+
+ Set updatedEmergencyContacts = new LinkedHashSet<>();
+ int i = index.getZeroBased();
+
+ for (EmergencyContact emergencyContact : personEmergencyContacts) {
+ if (i == 0) {
+ updatedEmergencyContacts.add(updatedEmergencyContact);
+ } else {
+ updatedEmergencyContacts.add(emergencyContact);
+ }
+ i = i - 1;
+ }
+ return updatedEmergencyContacts;
+ }
+
+ private static EmergencyContact createEditedEmergencyContact(EmergencyContact emergencyContactToEdit,
+ EditPersonDescriptor editPersonDescriptor) {
+ assert emergencyContactToEdit != null;
+
+ Name updatedName = editPersonDescriptor.getEmergencyContactName().orElse(emergencyContactToEdit.getName());
+ Phone updatedPhone = editPersonDescriptor.getEmergencyContactPhone()
+ .orElse(emergencyContactToEdit.getPhone());
+ Relationship updatedRelationship = editPersonDescriptor.getEmergencyContactRelationship()
+ .orElse(emergencyContactToEdit.getRelationship());
+
+ return new EmergencyContact(updatedName, updatedPhone, updatedRelationship);
+ }
+
+ private static Doctor createEditedDoctor(Doctor doctorToEdit,
+ EditPersonDescriptor editPersonDescriptor) {
+ assert doctorToEdit != null;
+
+ DoctorName updatedName = editPersonDescriptor.getDoctorName().orElse(doctorToEdit.getName());
+ Phone updatedPhone = editPersonDescriptor.getDoctorPhone()
+ .orElse(doctorToEdit.getPhone());
+ Email updatedEmail = editPersonDescriptor.getDoctorEmail()
+ .orElse(doctorToEdit.getEmail());
+
+ return new Doctor(updatedName, updatedPhone, updatedEmail);
+ }
+
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
@@ -88,22 +200,6 @@ public CommandResult execute(Model model) throws CommandException {
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
}
- /**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
- * edited with {@code editPersonDescriptor}.
- */
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
-
- Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
- Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
- }
-
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -129,7 +225,8 @@ public String toString() {
}
/**
- * Stores the details to edit the person with. Each non-empty field value will replace the
+ * Stores the details to edit the person with. Each non-empty field value will
+ * replace the
* corresponding field value of the person.
*/
public static class EditPersonDescriptor {
@@ -137,9 +234,17 @@ public static class EditPersonDescriptor {
private Phone phone;
private Email email;
private Address address;
+ private Index indexOfEmergencyContactToEdit;
+ private Name emergencyContactName;
+ private Phone emergencyContactPhone;
+ private Relationship emergencyContactRelationship;
+ private DoctorName doctorName;
+ private Phone doctorPhone;
+ private Email doctorEmail;
private Set tags;
- public EditPersonDescriptor() {}
+ public EditPersonDescriptor() {
+ }
/**
* Copy constructor.
@@ -150,6 +255,13 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setPhone(toCopy.phone);
setEmail(toCopy.email);
setAddress(toCopy.address);
+ setIndexOfEmergencyContactToEdit(toCopy.indexOfEmergencyContactToEdit);
+ setEmergencyContactName(toCopy.emergencyContactName);
+ setEmergencyContactPhone(toCopy.emergencyContactPhone);
+ setEmergencyContactRelationship(toCopy.emergencyContactRelationship);
+ setDoctorName(toCopy.doctorName);
+ setDoctorPhone(toCopy.doctorPhone);
+ setDoctorEmail(toCopy.doctorEmail);
setTags(toCopy.tags);
}
@@ -157,51 +269,102 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, indexOfEmergencyContactToEdit,
+ emergencyContactName, emergencyContactPhone, emergencyContactRelationship,
+ doctorName, doctorPhone, doctorEmail, tags);
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
}
public void setName(Name name) {
this.name = name;
}
- public Optional getName() {
- return Optional.ofNullable(name);
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
}
public void setPhone(Phone phone) {
this.phone = phone;
}
- public Optional getPhone() {
- return Optional.ofNullable(phone);
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
}
public void setEmail(Email email) {
this.email = email;
}
- public Optional getEmail() {
- return Optional.ofNullable(email);
+ public Optional getAddress() {
+ return Optional.ofNullable(address);
}
public void setAddress(Address address) {
this.address = address;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getEmergencyContactName() {
+ return Optional.ofNullable(emergencyContactName);
}
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ public void setEmergencyContactName(Name emergencyContactName) {
+ this.emergencyContactName = emergencyContactName;
+ }
+
+ public Optional getEmergencyContactPhone() {
+ return Optional.ofNullable(emergencyContactPhone);
+ }
+
+ public void setEmergencyContactPhone(Phone emergencyContactPhone) {
+ this.emergencyContactPhone = emergencyContactPhone;
+ }
+
+ public Optional getEmergencyContactRelationship() {
+ return Optional.ofNullable(emergencyContactRelationship);
+ }
+
+ public void setEmergencyContactRelationship(Relationship emergencyContactRelationship) {
+ this.emergencyContactRelationship = emergencyContactRelationship;
+ }
+
+ public Optional getIndexOfEmergencyContactToEdit() {
+ return Optional.ofNullable(indexOfEmergencyContactToEdit);
+ }
+
+ public void setIndexOfEmergencyContactToEdit(Index indexOfEmergencyContactToEdit) {
+ this.indexOfEmergencyContactToEdit = indexOfEmergencyContactToEdit;
+ }
+
+ public Optional getDoctorName() {
+ return Optional.ofNullable(doctorName);
+ }
+
+ public void setDoctorName(DoctorName doctorName) {
+ this.doctorName = doctorName;
+ }
+
+ public Optional getDoctorPhone() {
+ return Optional.ofNullable(doctorPhone);
+ }
+
+ public void setDoctorPhone(Phone doctorPhone) {
+ this.doctorPhone = doctorPhone;
+ }
+
+ public Optional getDoctorEmail() {
+ return Optional.ofNullable(doctorEmail);
+ }
+
+ public void setDoctorEmail(Email doctorEmail) {
+ this.doctorEmail = doctorEmail;
}
/**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * Returns an unmodifiable tag set, which throws
+ * {@code UnsupportedOperationException}
* if modification is attempted.
* Returns {@code Optional#empty()} if {@code tags} is null.
*/
@@ -209,6 +372,14 @@ public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ /**
+ * Sets {@code tags} to this object's {@code tags}.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public void setTags(Set tags) {
+ this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -225,6 +396,15 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
+ && Objects.equals(indexOfEmergencyContactToEdit,
+ otherEditPersonDescriptor.indexOfEmergencyContactToEdit)
+ && Objects.equals(emergencyContactName, otherEditPersonDescriptor.emergencyContactName)
+ && Objects.equals(emergencyContactPhone, otherEditPersonDescriptor.emergencyContactPhone)
+ && Objects.equals(emergencyContactRelationship,
+ otherEditPersonDescriptor.emergencyContactRelationship)
+ && Objects.equals(doctorName, otherEditPersonDescriptor.doctorName)
+ && Objects.equals(doctorPhone, otherEditPersonDescriptor.doctorPhone)
+ && Objects.equals(doctorEmail, otherEditPersonDescriptor.doctorEmail)
&& Objects.equals(tags, otherEditPersonDescriptor.tags);
}
@@ -235,6 +415,13 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
+ .add("index of emergency contact to edit", indexOfEmergencyContactToEdit)
+ .add("emergency contact name", emergencyContactName)
+ .add("emergency contact phone", emergencyContactPhone)
+ .add("emergency contact relationship", emergencyContactRelationship)
+ .add("doctor name", doctorName)
+ .add("doctor phone", doctorPhone)
+ .add("doctor email", doctorEmail)
.add("tags", tags)
.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..d16b235b1f2 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -16,8 +16,8 @@ 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"
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";
private final NameContainsKeywordsPredicate predicate;
diff --git a/src/main/java/seedu/address/logic/commands/FindDoctorCommand.java b/src/main/java/seedu/address/logic/commands/FindDoctorCommand.java
new file mode 100644
index 00000000000..9cfc2aa1d9b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindDoctorCommand.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.person.DoctorNameContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all persons with doctors whose names contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindDoctorCommand extends Command {
+
+ public static final String COMMAND_WORD = "finddoc";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons with assigned doctors whose names "
+ + "contain any of the specified keywords (case-insensitive) and displays them as a list with index "
+ + "numbers.\n\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n\n"
+ + "Example: " + COMMAND_WORD + " alice bob charlie";
+
+ private final DoctorNameContainsKeywordsPredicate predicate;
+
+ public FindDoctorCommand(DoctorNameContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FindDoctorCommand)) {
+ return false;
+ }
+
+ FindDoctorCommand otherFindDocCommand = (FindDoctorCommand) other;
+ return predicate.equals(otherFindDocCommand.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..881e8a9f7a8 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -9,7 +9,7 @@ public class HelpCommand extends Command {
public static final String COMMAND_WORD = "help";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n\n"
+ "Example: " + COMMAND_WORD;
public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
diff --git a/src/main/java/seedu/address/logic/commands/ListArchiveFilesCommand.java b/src/main/java/seedu/address/logic/commands/ListArchiveFilesCommand.java
new file mode 100644
index 00000000000..7aa1b27edef
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListArchiveFilesCommand.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Lists all archive files in the archive folder.
+ */
+public class ListArchiveFilesCommand extends Command {
+ public static final String COMMAND_WORD = "listarchives";
+
+ public static final String MESSAGE_SUCCESS = "Listed all archive files.";
+ public static final String MESSAGE_NO_ARCHIVE = "No archive files found.";
+ public static final String MESSAGE_FAILURE = "Failed to find archive files. Please try again later.";
+
+ private static final Logger logger = LogsCenter.getLogger(ListArchiveFilesCommand.class);
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Path archiveDir = model.getArchiveDirectoryPath();
+ if (!Files.exists(archiveDir)) {
+ logger.info("No archive directory found.");
+ throw new CommandException(MESSAGE_NO_ARCHIVE);
+ }
+
+ List archiveFiles = new ArrayList<>();
+
+ try (DirectoryStream stream = Files.newDirectoryStream(archiveDir, "*.json")) {
+ for (Path entry : stream) {
+ archiveFiles.add(entry.getFileName().toString());
+ }
+ } catch (IOException e) {
+ logger.severe("Failed to list archive files: " + e.getMessage());
+ throw new CommandException(MESSAGE_FAILURE);
+ }
+
+ if (archiveFiles.isEmpty()) {
+ throw new CommandException(MESSAGE_NO_ARCHIVE);
+ }
+
+ String resultMessage = String.join("\n", archiveFiles);
+
+ logger.info("Listed all archive files.");
+ return new CommandResult(MESSAGE_SUCCESS + "\n" + resultMessage);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..8c900f2728c 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -1,24 +1,52 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.Comparator;
import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.PersonComparators;
/**
- * Lists all persons in the address book to the user.
+ * Lists all persons in the address book to the user in a user-specified sorted order.
*/
public class ListCommand extends Command {
public static final String COMMAND_WORD = "list";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all persons in the address book.\n"
+ + "Parameters: [SORT_ORDER]\n"
+ + "Sort order can be one of the following:\n"
+ + "timeAdded asc, timeAdded desc, name asc, name desc\n"
+ + "Note: If no sort order is specified, the default is by time added (timeAdded) and ascending (asc).\n"
+ + "Example: " + COMMAND_WORD + " name asc";
+
public static final String MESSAGE_SUCCESS = "Listed all persons";
+ private final Comparator comparator;
+ /**
+ * Creates a ListCommand with the default comparator.
+ */
+ public ListCommand() {
+ // Default comparator is BY_ORDER_ADDED_REVERSED
+ this.comparator = PersonComparators.BY_ORDER_ADDED_REVERSED;
+ }
+
+ /**
+ * Creates a ListCommand with the specified comparator.
+ *
+ * @param comparator Comparator to sort the list of persons.
+ */
+ public ListCommand(Comparator comparator) {
+ this.comparator = comparator;
+ }
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.sortFilteredPersonList(comparator);
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/LoadArchiveCommand.java b/src/main/java/seedu/address/logic/commands/LoadArchiveCommand.java
new file mode 100644
index 00000000000..4c055919af9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LoadArchiveCommand.java
@@ -0,0 +1,77 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.storage.JsonAddressBookStorage;
+
+/**
+ * Loads an archive file and sets it as the current address book.
+ */
+public class LoadArchiveCommand extends Command {
+ public static final String COMMAND_WORD = "loadarchive";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Loads an archive file.\n\n"
+ + "Parameters: FILENAME\n\n"
+ + "Example: " + COMMAND_WORD + " addressbook-2024-11-06T20-29-05.7609475-example.json";
+
+ public static final String MESSAGE_SUCCESS = "Loaded archive file: %1$s";
+ public static final String MESSAGE_NOT_FOUND = "Archive file not found: %1$s";
+ public static final String MESSAGE_FAILURE = "Failed to load archive file: %1$s";
+
+ private static final Logger logger = LogsCenter.getLogger(LoadArchiveCommand.class);
+
+ private final Filename archiveFilename;
+
+ public LoadArchiveCommand(Filename archiveFilename) {
+ this.archiveFilename = archiveFilename;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Path archiveFile = Paths.get(model.getArchiveDirectoryPath().toString(), archiveFilename.toString());
+ if (!FileUtil.isFileExists(archiveFile)) {
+ logger.info("Archive file not found: " + archiveFilename);
+ throw new CommandException(String.format(MESSAGE_NOT_FOUND, archiveFilename));
+ }
+
+ try {
+ JsonAddressBookStorage storage = new JsonAddressBookStorage(archiveFile);
+ ReadOnlyAddressBook addressBook = storage.readAddressBook().orElseThrow(IOException::new);
+ model.setAddressBook(addressBook);
+ } catch (IOException | DataLoadingException e) {
+ logger.severe("Failed to load archive file: " + e.getMessage());
+ throw new CommandException(String.format(MESSAGE_FAILURE, archiveFilename));
+ }
+
+ logger.info("Loaded archive file: " + archiveFilename);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, archiveFilename));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof LoadArchiveCommand)) {
+ return false;
+ }
+
+ LoadArchiveCommand otherLoadArchiveCommand = (LoadArchiveCommand) other;
+ return archiveFilename.equals(otherLoadArchiveCommand.archiveFilename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..2f142d02268
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+
+/**
+ * Represents an Redo command that reverses the reversed last modification made to the address book.
+ * This command can be used to restore the previous state of the address book after an edit,
+ * addition, or deletion.
+ */
+public class RedoCommand extends Command {
+
+ public static final String COMMAND_WORD = "redo";
+ public static final String MESSAGE_SUCCESS = "Address book has redone previous command!";
+ public static final String MESSAGE_FAILURE = "There is no previous command to be redone!";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (model.canRedoAddressBook()) {
+ model.redoAddressBook();
+ return new CommandResult(MESSAGE_SUCCESS);
+ } else {
+ throw new CommandException(MESSAGE_FAILURE);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..276c32321bd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+
+/**
+ * Represents an Undo command that reverses the last modification made to the address book.
+ * This command can be used to restore the previous state of the address book after an edit,
+ * addition, or deletion.
+ */
+public class UndoCommand extends Command {
+
+ public static final String COMMAND_WORD = "undo";
+ public static final String MESSAGE_SUCCESS = "Address book has undone previous command!";
+ public static final String MESSAGE_FAILURE = "There is no current command to be undone!";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (model.canUndoAddressBook()) {
+ model.undoAddressBook();
+ return new CommandResult(MESSAGE_SUCCESS);
+ } else {
+ throw new CommandException(MESSAGE_FAILURE);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..7c63116d9d3 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -2,21 +2,32 @@
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_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
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.LinkedHashSet;
import java.util.Set;
import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
/**
@@ -24,38 +35,62 @@
*/
public class AddCommandParser implements Parser {
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values
+ * in the given
+ * {@code ArgumentMultimap}.
+ */
+ public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
/**
* Parses the given {@code String} of arguments in the context of the AddCommand
* and returns an AddCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_EMERGENCY_CONTACT_NAME,
+ PREFIX_EMERGENCY_CONTACT_PHONE, PREFIX_EMERGENCY_CONTACT_RELATIONSHIP, PREFIX_DOC_NAME,
+ PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ // Compulsory fields
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE, PREFIX_EMERGENCY_CONTACT_RELATIONSHIP,
+ PREFIX_DOC_NAME, PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL)
|| !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_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE, PREFIX_EMERGENCY_CONTACT_RELATIONSHIP,
+ PREFIX_DOC_NAME, PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL);
+
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+
+ Name ecName = ParserUtil.parseName(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).get());
+ Phone ecPhone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).get());
+ Relationship ecRelationship = ParserUtil.parseRelationship(
+ argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).get());
+ EmergencyContact emergencyContact = new EmergencyContact(ecName, ecPhone, ecRelationship);
+ Set emergencyContacts = new LinkedHashSet<>();
+ emergencyContacts.add(emergencyContact);
+
+ DoctorName doctorName = ParserUtil.parseDoctorName(argMultimap.getValue(PREFIX_DOC_NAME).get());
+ Phone doctorPhone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_DOC_PHONE).get());
+ Email doctorEmail = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_DOC_EMAIL).get());
+ Doctor doctor = new Doctor(doctorName, doctorPhone, doctorEmail);
+
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, address, emergencyContacts, doctor, tagList);
return new AddCommand(person);
}
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
}
diff --git a/src/main/java/seedu/address/logic/parser/AddEmergencyContactCommandParser.java b/src/main/java/seedu/address/logic/parser/AddEmergencyContactCommandParser.java
new file mode 100644
index 00000000000..eada0d8d7ea
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddEmergencyContactCommandParser.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddEmergencyContactCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
+
+/**
+ * Parses input arguments and creates a new EditCommand object
+ */
+public class AddEmergencyContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddEmergencyContactCommand
+ * and returns an AddEmergencyContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddEmergencyContactCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE,
+ PREFIX_EMERGENCY_CONTACT_RELATIONSHIP);
+
+ if (!AddCommandParser.arePrefixesPresent(argMultimap, PREFIX_EMERGENCY_CONTACT_NAME,
+ PREFIX_EMERGENCY_CONTACT_PHONE, PREFIX_EMERGENCY_CONTACT_RELATIONSHIP)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEmergencyContactCommand.MESSAGE_USAGE));
+ }
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEmergencyContactCommand.MESSAGE_USAGE), pe);
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE,
+ PREFIX_EMERGENCY_CONTACT_RELATIONSHIP);
+
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).get());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).get());
+ Relationship relationship = ParserUtil.parseRelationship(
+ argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).get());
+ EmergencyContact emergencyContact = new EmergencyContact(name, phone, relationship);
+ return new AddEmergencyContactCommand(index, emergencyContact);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..ca7eaec37ea 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,14 +9,22 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddEmergencyContactCommand;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.DeleteArchiveCommand;
import seedu.address.logic.commands.DeleteCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindDoctorCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ListArchiveFilesCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadArchiveCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -69,7 +77,28 @@ public Command parseCommand(String userInput) throws ParseException {
return new FindCommandParser().parse(arguments);
case ListCommand.COMMAND_WORD:
- return new ListCommand();
+ return new ListCommandParser().parse(arguments);
+
+ case ArchiveCommand.COMMAND_WORD:
+ return new ArchiveCommandParser().parse(arguments);
+
+ case ListArchiveFilesCommand.COMMAND_WORD:
+ return new ListArchiveFilesCommand();
+
+ case LoadArchiveCommand.COMMAND_WORD:
+ return new LoadArchiveCommandParser().parse(arguments);
+
+ case DeleteArchiveCommand.COMMAND_WORD:
+ return new DeleteArchiveCommandParser().parse(arguments);
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
+ case AddEmergencyContactCommand.COMMAND_WORD:
+ return new AddEmergencyContactCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
@@ -77,6 +106,9 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case FindDoctorCommand.COMMAND_WORD:
+ return new FindDoctorCommandParser().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/ArchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java
new file mode 100644
index 00000000000..e7012d0a3fa
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java
@@ -0,0 +1,26 @@
+package seedu.address.logic.parser;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.ArchiveCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ArchiveCommand object
+ */
+public class ArchiveCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ArchiveCommand
+ * and returns a ArchiveCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ArchiveCommand parse(String args) throws ParseException {
+ if (args.isBlank()) {
+ return new ArchiveCommand();
+ }
+
+ Filename filename = ParserUtil.parseFilename(args);
+ return new ArchiveCommand(filename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..d020df75927 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -11,5 +11,11 @@ public class CliSyntax {
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
-
+ public static final Prefix PREFIX_EMERGENCY_CONTACT_TO_EDIT = new Prefix("ec/");
+ public static final Prefix PREFIX_EMERGENCY_CONTACT_NAME = new Prefix("ecname/");
+ public static final Prefix PREFIX_EMERGENCY_CONTACT_PHONE = new Prefix("ecphone/");
+ public static final Prefix PREFIX_EMERGENCY_CONTACT_RELATIONSHIP = new Prefix("ecrs/");
+ public static final Prefix PREFIX_DOC_NAME = new Prefix("dname/");
+ public static final Prefix PREFIX_DOC_PHONE = new Prefix("dphone/");
+ public static final Prefix PREFIX_DOC_EMAIL = new Prefix("demail/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteArchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteArchiveCommandParser.java
new file mode 100644
index 00000000000..1dd79c739cc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteArchiveCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.DeleteArchiveCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteArchiveCommand object
+ */
+public class DeleteArchiveCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteArchiveCommand
+ * and returns a DeleteArchiveCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteArchiveCommand parse(String args) throws ParseException {
+ Filename filename;
+ try {
+ filename = ParserUtil.parseFilename(args);
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ pe.getMessage() + "\n\n" + DeleteArchiveCommand.MESSAGE_USAGE), pe);
+ }
+ return new DeleteArchiveCommand(filename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 3527fe76a3e..4eada2a40f0 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,9 +1,12 @@
package seedu.address.logic.parser;
+import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteCommand.DeleteCommandDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -17,13 +20,34 @@ public class DeleteCommandParser implements Parser {
* @throws ParseException if the user input does not conform the expected format
*/
public DeleteCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EMERGENCY_CONTACT_TO_EDIT);
+
+ Index personIndex;
try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
+ personIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
}
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_EMERGENCY_CONTACT_TO_EDIT);
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_TO_EDIT).isPresent()) {
+ try {
+ Index emergencyContactIndex = ParserUtil.parseIndex(argMultimap.getValue(
+ PREFIX_EMERGENCY_CONTACT_TO_EDIT).get());
+ deleteCommandDescriptor.setEmergencyContactIndex(emergencyContactIndex);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ return new DeleteCommand(personIndex, deleteCommandDescriptor);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..b56d800fa2f 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -3,7 +3,14 @@
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_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
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;
@@ -32,7 +39,11 @@ 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_EMERGENCY_CONTACT_TO_EDIT,
+ PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE,
+ PREFIX_EMERGENCY_CONTACT_RELATIONSHIP, PREFIX_DOC_NAME, PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL,
+ PREFIX_TAG);
Index index;
@@ -42,10 +53,63 @@ 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_EMERGENCY_CONTACT_TO_EDIT,
+ PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE, PREFIX_EMERGENCY_CONTACT_RELATIONSHIP,
+ PREFIX_DOC_NAME, PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL);
+
+ verifyEmergencyContactFields(args, argMultimap);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ setEditPersonDescriptorPersonFields(editPersonDescriptor, argMultimap);
+ setEditPersonDescriptorEmergencyContactFields(editPersonDescriptor, argMultimap);
+ setEditPersonDescriptorDoctorFields(editPersonDescriptor, argMultimap);
+
+ if (!editPersonDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(index, editPersonDescriptor);
+ }
+ private void setEditPersonDescriptorDoctorFields(EditPersonDescriptor editPersonDescriptor,
+ ArgumentMultimap argMultimap) throws ParseException {
+ if (argMultimap.getValue(PREFIX_DOC_NAME).isPresent()) {
+ editPersonDescriptor.setDoctorName(ParserUtil.parseDoctorName(
+ argMultimap.getValue(PREFIX_DOC_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DOC_PHONE).isPresent()) {
+ editPersonDescriptor.setDoctorPhone(ParserUtil.parsePhone(
+ argMultimap.getValue(PREFIX_DOC_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DOC_EMAIL).isPresent()) {
+ editPersonDescriptor.setDoctorEmail(ParserUtil.parseEmail(
+ argMultimap.getValue(PREFIX_DOC_EMAIL).get()));
+ }
+ }
+
+ private void setEditPersonDescriptorEmergencyContactFields(EditPersonDescriptor editPersonDescriptor,
+ ArgumentMultimap argMultimap) throws ParseException {
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_TO_EDIT).isPresent()) {
+ editPersonDescriptor.setIndexOfEmergencyContactToEdit(
+ ParserUtil.parseIndex(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_TO_EDIT).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).isPresent()) {
+ editPersonDescriptor.setEmergencyContactName(
+ ParserUtil.parseName(argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).isPresent()) {
+ editPersonDescriptor.setEmergencyContactPhone(ParserUtil.parsePhone(
+ argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).isPresent()) {
+ editPersonDescriptor.setEmergencyContactRelationship(ParserUtil.parseRelationship(
+ argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).get()));
+ }
+ }
+
+ private void setEditPersonDescriptorPersonFields(EditPersonDescriptor editPersonDescriptor,
+ ArgumentMultimap argMultimap) throws ParseException {
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
@@ -59,12 +123,35 @@ public EditCommand parse(String args) throws ParseException {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+ }
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ private void verifyEmergencyContactFields(String args, ArgumentMultimap argMultimap) throws ParseException {
+ if (!isEmergencyContactIndexProvided(args, argMultimap)) {
+ throw new ParseException(EditCommand.MESSAGE_EMERGENCY_CONTACT_FIELDS_INVALID);
}
+ if (!isEmergencyContactFieldsProvided(args, argMultimap)) {
+ throw new ParseException(EditCommand.MESSAGE_EMERGENCY_CONTACT_NOT_EDITED);
+ }
+ }
- return new EditCommand(index, editPersonDescriptor);
+ private Boolean isEmergencyContactFieldsProvided(String args, ArgumentMultimap argMultimap) {
+ if (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_TO_EDIT).isPresent()
+ && !argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).isPresent()
+ && !argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).isPresent()
+ && !argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).isPresent()) {
+ return false;
+ }
+ return true;
+ }
+
+ private Boolean isEmergencyContactIndexProvided(String args, ArgumentMultimap argMultimap) {
+ if (!argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_TO_EDIT).isPresent()
+ && (argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_NAME).isPresent()
+ || argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_PHONE).isPresent()
+ || argMultimap.getValue(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP).isPresent())) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/src/main/java/seedu/address/logic/parser/FindDoctorCommandParser.java b/src/main/java/seedu/address/logic/parser/FindDoctorCommandParser.java
new file mode 100644
index 00000000000..9ca1d54cca4
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindDoctorCommandParser.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.FindDoctorCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.DoctorNameContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindDoctorCommand object
+ */
+public class FindDoctorCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindDoctorCommand
+ * and returns a FindDoctorCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindDoctorCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindDoctorCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ return new FindDoctorCommand(new DoctorNameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
new file mode 100644
index 00000000000..04cd629f72b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.PersonComparators;
+
+/**
+ * Parses input arguments and creates a new ListCommand object
+ */
+public class ListCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListCommand
+ * and returns a ListCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ListCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+
+ switch (trimmedArgs.toLowerCase()) {
+ case "":
+ // Default sort order is timeAdded asc
+ return new ListCommand(PersonComparators.BY_ORDER_ADDED_REVERSED);
+ case "timeadded", "timeadded asc":
+ return new ListCommand(PersonComparators.BY_ORDER_ADDED_REVERSED);
+ case "timeadded desc":
+ return new ListCommand(PersonComparators.BY_ORDER_ADDED);
+ case "name", "name asc":
+ return new ListCommand(PersonComparators.BY_NAME);
+ case "name desc":
+ return new ListCommand(PersonComparators.BY_NAME_REVERSED);
+ default:
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/LoadArchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/LoadArchiveCommandParser.java
new file mode 100644
index 00000000000..4fd1aedfde1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LoadArchiveCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.LoadArchiveCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LoadArchiveCommand object
+ */
+public class LoadArchiveCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LoadArchiveCommand
+ * and returns a LoadArchiveCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LoadArchiveCommand parse(String args) throws ParseException {
+ Filename filename;
+ try {
+ filename = ParserUtil.parseFilename(args);
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ pe.getMessage() + "\n\n" + LoadArchiveCommand.MESSAGE_USAGE), pe);
+ }
+ return new LoadArchiveCommand(filename);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..5e1719fa37a 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -6,13 +6,16 @@
import java.util.HashSet;
import java.util.Set;
+import seedu.address.commons.core.filename.Filename;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
/**
@@ -25,6 +28,7 @@ public class ParserUtil {
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
* trimmed.
+ *
* @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
*/
public static Index parseIndex(String oneBasedIndex) throws ParseException {
@@ -35,6 +39,24 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
+ /**
+ * Parses {@code String filename} into a {@code Filename}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the specified filename is invalid.
+ */
+ public static Filename parseFilename(String filename) throws ParseException {
+ requireNonNull(filename);
+ String trimmedFilename = filename.trim();
+ if (trimmedFilename.isBlank()) {
+ throw new ParseException(Filename.MESSAGE_CONSTRAINTS_BLANK);
+ }
+ if (!Filename.isValidFilename(trimmedFilename)) {
+ throw new ParseException(Filename.MESSAGE_CONSTRAINTS);
+ }
+ return new Filename(trimmedFilename);
+ }
+
/**
* Parses a {@code String name} into a {@code Name}.
* Leading and trailing whitespaces will be trimmed.
@@ -50,6 +72,21 @@ public static Name parseName(String name) throws ParseException {
return new Name(trimmedName);
}
+ /**
+ * Parses a {@code String doctorName} into a {@code DoctorName}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code doctorName} is invalid.
+ */
+ public static DoctorName parseDoctorName(String doctorName) throws ParseException {
+ requireNonNull(doctorName);
+ String trimmedName = doctorName.trim();
+ if (!DoctorName.isValidName(trimmedName)) {
+ throw new ParseException(DoctorName.MESSAGE_CONSTRAINTS);
+ }
+ return new DoctorName(trimmedName);
+ }
+
/**
* Parses a {@code String phone} into a {@code Phone}.
* Leading and trailing whitespaces will be trimmed.
@@ -95,6 +132,24 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String relationship} into a {@code Relationship}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code relationship} is invalid.
+ */
+ public static Relationship parseRelationship(String relationship) throws ParseException {
+ requireNonNull(relationship);
+ String trimmedRelationship = relationship.trim();
+ if (!Relationship.isAlphanumericRelationship(trimmedRelationship)) {
+ throw new ParseException(Relationship.ALPHANUMERIC_CONSTRAINTS);
+ }
+ if (!Relationship.isValidRelationship(trimmedRelationship)) {
+ throw new ParseException(Relationship.RELATIONSHIP_TYPE_CONSTRAINTS);
+ }
+ return new Relationship(trimmedRelationship);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..1fba7acf35f 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,17 +1,22 @@
package seedu.address.model;
+import java.io.IOException;
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.commons.core.filename.Filename;
import seedu.address.model.person.Person;
/**
* The API of the Model component.
*/
public interface Model {
- /** {@code Predicate} that always evaluate to true */
+ /**
+ * {@code Predicate} that always evaluate to true
+ */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
/**
@@ -49,9 +54,58 @@ public interface Model {
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
- /** Returns the AddressBook */
+ /**
+ * Returns the AddressBook
+ */
ReadOnlyAddressBook getAddressBook();
+ /**
+ * Returns the archive directory path.
+ */
+ Path getArchiveDirectoryPath();
+
+ /**
+ * Archives the address book.
+ *
+ * @param filename the name of the file to archive the address book to.
+ * @throws IOException if there was an error writing to the file.
+ */
+ void archiveAddressBook(Filename filename) throws IOException;
+
+ /**
+ * Undoes the previous command that modified the state or storage of the address book.
+ */
+ void undoAddressBook();
+
+ /**
+ * Returns true if there is a previous state in the address book that can be undone.
+ *
+ * @return true if undo can be performed, false otherwise.
+ */
+ boolean canUndoAddressBook();
+
+ /**
+ * Saves the current state of the address book to history.
+ */
+ void saveAddressBook();
+
+ /**
+ * Restores the next state of the address book (redo).
+ * This method reverts the address book to a state that was undone
+ * and is available in the redo history, if such a state exists.
+ * If there is no state available to redo, no changes will be made.
+ */
+ void redoAddressBook();
+
+ /**
+ * Returns true if there is a future state available to redo.
+ * This method checks whether the redo history contains a state
+ * that can be restored, meaning if the user has undone a state before
+ * and can now move forward to that state again.
+ */
+ boolean canRedoAddressBook();
+
+
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
@@ -76,12 +130,22 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
- /** Returns an unmodifiable view of the filtered person list */
+ /**
+ * Returns an unmodifiable view of the filtered person list
+ */
ObservableList getFilteredPersonList();
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ *
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Sorts the filtered person list using the given {@code comparator}.
+ *
+ * @throws NullPointerException if {@code comparator} is null.
+ */
+ void sortFilteredPersonList(Comparator comparator);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..57cb7ef92bc 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -1,27 +1,40 @@
package seedu.address.model;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+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.commons.core.filename.Filename;
+import seedu.address.commons.util.FileUtil;
import seedu.address.model.person.Person;
/**
* Represents the in-memory model of the address book data.
*/
public class ModelManager implements Model {
+ public static final String ARCHIVE_DIRNAME = "archive";
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
- private final AddressBook addressBook;
+ // Stack to store the history of address book states for undo functionality
+ private final VersionedAddressBook versionedAddressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final SortedList sortedPersons;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -31,9 +44,10 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
- this.addressBook = new AddressBook(addressBook);
+ this.versionedAddressBook = new VersionedAddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredPersons = new FilteredList<>(this.versionedAddressBook.getPersonList());
+ sortedPersons = new SortedList<>(filteredPersons);
}
public ModelManager() {
@@ -79,36 +93,115 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
+ this.versionedAddressBook.resetData(addressBook); // Reset the versioned address book's data
+ saveAddressBook();
}
@Override
public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
+ return versionedAddressBook;
+ }
+
+ @Override
+ public Path getArchiveDirectoryPath() {
+ Path source = this.getAddressBookFilePath();
+ assert source != null : "Address book file path is null";
+
+ Path archiveDir = Paths.get(source.getParent().toString(), ARCHIVE_DIRNAME);
+
+ if (!Files.exists(archiveDir)) {
+ logger.info("No archive directory found.");
+ }
+
+ return archiveDir;
+ }
+
+ @Override
+ public void archiveAddressBook(Filename filename) throws IOException {
+ Path source = this.getAddressBookFilePath();
+ assert source != null : "Address book file path is null";
+
+ String timestamp = LocalDateTime.now()
+ .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+ .replace(":", "-"); // : is not allowed in filenames in Windows
+ String archiveFilename = source.getFileName().toString().replace(".json", "") + "-"
+ + timestamp + (filename.toString().isEmpty() ? "" : "-" + filename) + ".json";
+
+ Path destination = Paths.get(this.getArchiveDirectoryPath().toString(), archiveFilename);
+
+ FileUtil.createParentDirsOfFile(destination);
+ Files.copy(source, destination, REPLACE_EXISTING);
+ logger.info("Address book has been archived!");
}
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
- return addressBook.hasPerson(person);
+ return versionedAddressBook.hasPerson(person);
}
@Override
public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ versionedAddressBook.removePerson(target);
+ // No need updateFilteredPList as FilteredList<> auto-updates from addressBook4
+ saveAddressBook();
}
@Override
public void addPerson(Person person) {
- addressBook.addPerson(person);
+ versionedAddressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ // FilteredList<> auto-updates from addressBook, BUT may not show, if alr looking at filtered list
+ saveAddressBook();
}
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
+ versionedAddressBook.setPerson(target, editedPerson);
+ saveAddressBook();
+ }
- addressBook.setPerson(target, editedPerson);
+ // ============ Undo and Redo Methods ================================================================
+
+ /**
+ * Commits the current state of the address book to history.
+ */
+ @Override
+ public void saveAddressBook() {
+ versionedAddressBook.save();
+ }
+
+ /**
+ * Restores the previous state of the address book (undo).
+ */
+ @Override
+ public void undoAddressBook() {
+ if (canUndoAddressBook()) {
+ versionedAddressBook.undo();
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ }
+ }
+
+ /**
+ * Returns true if there is a previous state to undo.
+ */
+ @Override
+ public boolean canUndoAddressBook() {
+ return versionedAddressBook.canUndo();
+ }
+
+ @Override
+ public void redoAddressBook() {
+ if (canRedoAddressBook()) {
+ versionedAddressBook.redo();
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ }
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ return versionedAddressBook.canRedo();
}
//=========== Filtered Person List Accessors =============================================================
@@ -119,7 +212,7 @@ public void setPerson(Person target, Person editedPerson) {
*/
@Override
public ObservableList getFilteredPersonList() {
- return filteredPersons;
+ return sortedPersons;
}
@Override
@@ -128,21 +221,25 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ @Override
+ public void sortFilteredPersonList(Comparator comparator) {
+ requireNonNull(comparator);
+ sortedPersons.setComparator(comparator);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
-
- // instanceof handles nulls
if (!(other instanceof ModelManager)) {
return false;
}
ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
+ return versionedAddressBook.equals(otherModelManager.versionedAddressBook)
&& userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
+ && filteredPersons.equals(otherModelManager.filteredPersons)
+ && sortedPersons.equals(otherModelManager.sortedPersons);
}
-
}
diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java
new file mode 100644
index 00000000000..1e7dc26364f
--- /dev/null
+++ b/src/main/java/seedu/address/model/VersionedAddressBook.java
@@ -0,0 +1,80 @@
+package seedu.address.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * VersionedAddressBook is an extension of the AddressBook that supports undo/redo functionality.
+ * It stores a history of AddressBook states and allows for reverting to previous states or restoring
+ * undone states.
+ */
+public class VersionedAddressBook extends AddressBook {
+ private final List addressBookStateList;
+ private int currentStatePointer;
+
+ /**
+ * Creates a VersionedAddressBook with the initial state.
+ * @param initialState The initial state of the AddressBook.
+ */
+ public VersionedAddressBook(ReadOnlyAddressBook initialState) {
+ super(initialState);
+ addressBookStateList = new ArrayList<>();
+ addressBookStateList.add(new AddressBook(initialState));
+ currentStatePointer = 0;
+ }
+
+ /**
+ * Saves the current address book state to the state list.
+ * It removes all future states in the history after the current state when a new change is saved.
+ * This ensures that any redoable history is discarded once a new change is made after an undo.
+ */
+ public void save() {
+ if (currentStatePointer < addressBookStateList.size() - 1) {
+ addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear();
+ }
+
+ // Check if the current state is same as last saved state -> avoid saving a duplicate state
+ if (!addressBookStateList.get(currentStatePointer).equals(new AddressBook(this))) {
+ // Save the current state
+ addressBookStateList.add(new AddressBook(this));
+ currentStatePointer++;
+ }
+ }
+
+ /**
+ * Restores the previous state of the address book.
+ * Moves one step back in the history to undo the last change.
+ */
+ public void undo() {
+ if (canUndo()) {
+ currentStatePointer--;
+ resetData(addressBookStateList.get(currentStatePointer));
+ }
+ }
+
+ /**
+ * Checks if there is a previous state to undo.
+ */
+ public boolean canUndo() {
+ return currentStatePointer > 0;
+ }
+
+ /**
+ * Restores the next state of the address book.
+ * Moves one step forward in the history to redo the last undone change.
+ */
+ public void redo() {
+ if (canRedo()) {
+ currentStatePointer++;
+ resetData(addressBookStateList.get(currentStatePointer));
+ }
+ }
+
+ /**
+ * Checks if there is a next state to redo.
+ */
+ public boolean canRedo() {
+ return currentStatePointer < addressBookStateList.size() - 1;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Doctor.java b/src/main/java/seedu/address/model/person/Doctor.java
new file mode 100644
index 00000000000..084885a8731
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Doctor.java
@@ -0,0 +1,108 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Objects;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Represents a Person in the address book.
+ * Guarantees: details are present and not null, field values are validated,
+ * immutable.
+ */
+public class Doctor {
+
+ // Identity fields
+ private final DoctorName name;
+ private final Phone phone;
+ private final Email email;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Doctor(DoctorName name, Phone phone, Email email) {
+ requireAllNonNull(name, phone, email);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+
+ }
+
+ /**
+ * Returns the name of the doctor.
+ *
+ * @return Name of the doctor.
+ */
+ public DoctorName getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the phone number of the doctor.
+ *
+ * @return Phone number of the doctor.
+ */
+ public Phone getPhone() {
+ return this.phone;
+ }
+
+ /**
+ * Returns the email of the doctor.
+ *
+ * @return Email of the doctor.
+ */
+ public Email getEmail() {
+ return this.email;
+ }
+
+ /**
+ * Returns true if both doctors have the same name.
+ * This defines a weaker notion of equality between two doctors.
+ */
+ public boolean isSamePerson(Doctor otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getName().equals(getName());
+ }
+
+ /**
+ * Returns true if both doctors have the same identity and data fields.
+ * This defines a stronger notion of equality between two doctors.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Doctor)) {
+ return false;
+ }
+
+ Doctor otherDoctor = (Doctor) other;
+ return name.equals(otherDoctor.name)
+ && phone.equals(otherDoctor.phone)
+ && email.equals(otherDoctor.email);
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(getName(), getPhone(), getEmail());
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/DoctorName.java b/src/main/java/seedu/address/model/person/DoctorName.java
new file mode 100644
index 00000000000..f4a0837577a
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/DoctorName.java
@@ -0,0 +1,69 @@
+package seedu.address.model.person;
+
+/**
+ * Represents a Doctor's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
+ */
+public class DoctorName extends Name {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Names should not be blank and should only contain alphanumeric characters, spaces, the words 'd/o' or "
+ + "'s/o' or the following special characters: - . ( ) @ '";
+
+ /*
+ * The first character of the address must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}-.()@/'][\\p{Alnum}-.()@/' ]*";
+
+ public final String doctorName;
+ /**
+ * Constructs a {@code DoctorName}.
+ *
+ * @param name A valid name.
+ */
+ public DoctorName(String name) {
+ super(name);
+ this.doctorName = "Dr " + name;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidName(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /*
+ * Returns the name of the doctor, with the "Dr " prefix.
+ */
+ public String getDoctorName() {
+ return doctorName;
+ }
+
+ @Override
+ public String toString() {
+ return fullName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DoctorName)) {
+ return false;
+ }
+
+ DoctorName otherName = (DoctorName) other;
+ return fullName.equals(otherName.fullName);
+ }
+
+ @Override
+ public int hashCode() {
+ return fullName.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..bdc0766c7e1
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicate.java
@@ -0,0 +1,45 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class DoctorNameContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public DoctorNameContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(
+ person.getDoctor().getName().fullName, keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DoctorNameContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ DoctorNameContainsKeywordsPredicate otherPredicate = (DoctorNameContainsKeywordsPredicate) other;
+ return keywords.equals(otherPredicate.keywords);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keywords", keywords).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index c62e512bc29..85ab9294e97 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -14,7 +14,7 @@ public class Email {
+ "and adhere to the following constraints:\n"
+ "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
+ "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special "
- + "characters.\n"
+ + "characters or have more than 1 special character in a row.\n"
+ "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels "
+ "separated by periods.\n"
+ "The domain name must:\n"
diff --git a/src/main/java/seedu/address/model/person/EmergencyContact.java b/src/main/java/seedu/address/model/person/EmergencyContact.java
new file mode 100644
index 00000000000..4b79e8143ac
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmergencyContact.java
@@ -0,0 +1,102 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents an Emergency Contact in the address book.
+ * Guarantees: fields are present and not null, field values are validated.
+ */
+public class EmergencyContact {
+ // Identity fields
+ private final Name name;
+ private final Phone phone;
+ private final Relationship relationship;
+
+ /**
+ * Constructs an {@code EmergencyContact}.
+ *
+ * @param name A valid name.
+ * @param phone A valid phone number.
+ * @param relationship A valid relationship.
+ */
+ public EmergencyContact(Name name, Phone phone, Relationship relationship) {
+ requireAllNonNull(name, phone, relationship);
+ this.name = name;
+ this.phone = phone;
+ this.relationship = relationship;
+ }
+
+ /**
+ * Returns the name of the emergency contact.
+ *
+ * @return Name of the emergency contact.
+ */
+ public Name getName() {
+ return name;
+ }
+
+ /**
+ * Returns the phone number of the emergency contact.
+ *
+ * @return Phone number of the emergency contact.
+ */
+ public Phone getPhone() {
+ return phone;
+ }
+
+ /**
+ * Returns the relationship with the emergency contact.
+ *
+ * @return Relationship with the emergency contact.
+ */
+ public Relationship getRelationship() {
+ return relationship;
+ }
+
+ /**
+ * Returns true if both persons have the same name.
+ * This defines a weaker notion of equality between two persons.
+ */
+ public boolean isSamePerson(EmergencyContact otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getPhone().equals(getPhone());
+ }
+
+ /**
+ * Returns true if both persons have the same identity and data fields.
+ * This defines a stronger notion of equality between two persons.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EmergencyContact)) {
+ return false;
+ }
+
+ EmergencyContact otherPerson = (EmergencyContact) other;
+ return name.equals(otherPerson.name)
+ && phone.equals(otherPerson.phone)
+ && relationship.equals(otherPerson.relationship);
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(name, phone, relationship);
+ }
+
+ @Override
+ public String toString() {
+ return "Name: " + name + "; Phone: " + phone + "; Relationship: " + relationship + ";";
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..5df0a947ec0 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -7,16 +7,17 @@
* Represents a Person's name in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
-public class Name {
+public class Name implements Comparable {
public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
+ "Names should not be blank and should only contain alphanumeric characters, spaces, the words 'd/o' or "
+ + "'s/o' or the following special characters: - . ( ) @ '";
/*
* 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 = "[\\p{Alnum}-.()@/'][\\p{Alnum}-.()@/' ]*";
public final String fullName;
@@ -33,11 +34,17 @@ public Name(String name) {
/**
* Returns true if a given string is a valid name.
+ *
+ * @param test String to test.
*/
public static boolean isValidName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ @Override
+ public int compareTo(Name otherName) {
+ return fullName.compareTo(otherName.fullName);
+ }
@Override
public String toString() {
@@ -51,11 +58,10 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof Name)) {
+ if (!(other instanceof Name otherName)) {
return false;
}
- Name otherName = (Name) other;
return fullName.equals(otherName.fullName);
}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..769264d3199 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -2,17 +2,22 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
+import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.exceptions.EmergencyContactNotFoundException;
import seedu.address.model.tag.Tag;
/**
* Represents a Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
+ * Guarantees: details are present and not null, field values are validated,
+ * immutable.
*/
public class Person {
@@ -23,18 +28,27 @@ public class Person {
// Data fields
private final Address address;
+ // LinkedHashSet preserves the order of emergency contacts such that
+ // the index of the emergency contact can be reliably access by the Delete command.
+ private final Set emergencyContacts = new LinkedHashSet<>();
+ private final Doctor doctor;
private final Set tags = new HashSet<>();
+ private final LocalDateTime dateAdded;
/**
* 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 emergencyContacts, Doctor doctor, Set tags) {
+ requireAllNonNull(name, phone, email, address, emergencyContacts, tags);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.emergencyContacts.addAll(emergencyContacts);
+ this.doctor = doctor;
this.tags.addAll(tags);
+ this.dateAdded = LocalDateTime.now();
}
public Name getName() {
@@ -52,15 +66,80 @@ public Email getEmail() {
public Address getAddress() {
return address;
}
+ public EmergencyContact getFirstEmergencyContact() {
+ return emergencyContacts.iterator().next();
+ }
+
+ public Set getEmergencyContacts() {
+ return Collections.unmodifiableSet(emergencyContacts);
+ }
+
+ public EmergencyContact getEmergencyContact(Index oneBasedIndex)
+ throws EmergencyContactNotFoundException {
+ int i = oneBasedIndex.getZeroBased();
+ for (EmergencyContact emergencyContact : emergencyContacts) {
+ if (i == 0) {
+ return emergencyContact;
+ }
+ i = i - 1;
+ }
+ throw new EmergencyContactNotFoundException();
+ }
+
+ /**
+ * Returns a copy of emergencyContacts with
+ * @param emergencyContactToRemove removed
+ * @return a copy of emergencyContacts with {@code emergencyContactToRemove} removed
+ */
+ public Set removeEmergencyContact(EmergencyContact emergencyContactToRemove) {
+ Set updatedEmergencyContacts = new LinkedHashSet<>();
+ for (EmergencyContact emergencyContact : emergencyContacts) {
+ if (!emergencyContact.equals(emergencyContactToRemove)) {
+ updatedEmergencyContacts.add(emergencyContact);
+ }
+ }
+ return updatedEmergencyContacts;
+ }
+
+ public Boolean hasOnlyOneEmergencyContact() {
+ return emergencyContacts.size() == 1;
+ }
+
+ /**
+ * Checks if the specified emergency contact exists in the list of emergency contacts.
+ *
+ * @param emergencyContactToCheck The emergency contact to check for.
+ * @return {@code true} if the emergency contact exists in the list, {@code false} otherwise.
+ */
+ public Boolean hasEmergencyContact(EmergencyContact emergencyContactToCheck) {
+ for (EmergencyContact emergencyContact : emergencyContacts) {
+ if (emergencyContact.equals((emergencyContactToCheck))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Doctor getDoctor() {
+ return doctor;
+ }
/**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * Returns an immutable tag set, which throws
+ * {@code UnsupportedOperationException}
* if modification is attempted.
*/
public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ /**
+ * Returns the datetime the person was added to the address book.
+ */
+ public LocalDateTime getDateAdded() {
+ return dateAdded;
+ }
+
/**
* Returns true if both persons have the same name.
* This defines a weaker notion of equality between two persons.
@@ -71,7 +150,7 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getPhone().equals(getPhone());
}
/**
@@ -94,13 +173,15 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
+ && emergencyContacts.equals(otherPerson.emergencyContacts)
+ && doctor.equals(otherPerson.doctor)
&& tags.equals(otherPerson.tags);
}
@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, emergencyContacts, doctor, tags);
}
@Override
@@ -110,6 +191,8 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
+ .add("emergency contacts", emergencyContacts)
+ .add("doctor", doctor)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/model/person/PersonComparators.java b/src/main/java/seedu/address/model/person/PersonComparators.java
new file mode 100644
index 00000000000..89947ef02ff
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PersonComparators.java
@@ -0,0 +1,13 @@
+package seedu.address.model.person;
+
+import java.util.Comparator;
+
+/**
+ * Contains a list of comparators for {@code Person}.
+ */
+public class PersonComparators {
+ public static final Comparator BY_ORDER_ADDED = Comparator.comparing(Person::getDateAdded).reversed();
+ public static final Comparator BY_ORDER_ADDED_REVERSED = Comparator.comparing(Person::getDateAdded);
+ public static final Comparator BY_NAME = Comparator.comparing(Person::getName);
+ public static final Comparator BY_NAME_REVERSED = Comparator.comparing(Person::getName).reversed();
+}
diff --git a/src/main/java/seedu/address/model/person/Relationship.java b/src/main/java/seedu/address/model/person/Relationship.java
new file mode 100644
index 00000000000..85653d18cfb
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Relationship.java
@@ -0,0 +1,117 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.Objects;
+
+/**
+ * Represents a Relationship between a Person and their EmergencyContact
+ * Guarantees: immutable; is valid as declared in {@link #isValidRelationship(String)}
+ * and {@link #isAlphanumericRelationship(String)}.
+ */
+public class Relationship {
+ public static final String RELATIONSHIP_TYPE_CONSTRAINTS =
+ "Relationship type should be Parent, Child, Sibling, Spouse, Friend, "
+ + "Grandparent or Relative or their gendered variants";
+ public static final String ALPHANUMERIC_CONSTRAINTS = "Relationship name "
+ + "should only contain alphanumeric characters and spaces, and it should not be blank";
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
+ public final String relationship;
+
+ /**
+ * Constructs a {@code Relationship}.
+ *
+ * @param relationship A valid relationship type.
+ */
+ public Relationship(String relationship) {
+ requireNonNull(relationship);
+ checkArgument(isAlphanumericRelationship(relationship), ALPHANUMERIC_CONSTRAINTS);
+ checkArgument(isValidRelationship(relationship), RELATIONSHIP_TYPE_CONSTRAINTS);
+ this.relationship = getRelationshipString(relationship);
+ }
+
+ private static String getRelationshipString(String relationship) {
+ assert isValidRelationship(relationship);
+ return relationship.substring(0, 1).toUpperCase() + relationship.substring(1).toLowerCase();
+ }
+
+ /**
+ * Returns true if a given string is a valid relationship name alphanumerically.
+ */
+ public static boolean isAlphanumericRelationship(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns true if the given string is a valid relationship type.
+ *
+ * @param relationship The relationship string to test.
+ * @return True if the string is a valid relationship type, false otherwise.
+ */
+ public static Boolean isValidRelationship(String relationship) {
+ for (RelationshipType relationshipType : RelationshipType.values()) {
+ if (relationshipType.name().equals(relationship.toUpperCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return relationship;
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(relationship);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Relationship)) {
+ return false;
+ }
+
+ Relationship otherRelationship = (Relationship) other;
+ return relationship.equals(otherRelationship.relationship);
+ }
+
+ /**
+ * Enum representing different types of relationships.
+ */
+ private enum RelationshipType {
+ PARENT,
+ MOTHER,
+ FATHER,
+ CHILD,
+ SON,
+ DAUGHTER,
+ SIBLING,
+ BROTHER,
+ SISTER,
+ FRIEND,
+ SPOUSE,
+ HUSBAND,
+ WIFE,
+ PARTNER,
+ COUSIN,
+ RELATIVE,
+ UNCLE,
+ AUNT,
+ GRANDPARENT,
+ GRANDMOTHER,
+ GRANDFATHER,
+ GRANDCHILD,
+ GRANDSON,
+ GRANDDAUGHTER;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/EmergencyContactNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/EmergencyContactNotFoundException.java
new file mode 100644
index 00000000000..71e9f54d2ae
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/EmergencyContactNotFoundException.java
@@ -0,0 +1,7 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified EmergencyContact.
+ */
+public class EmergencyContactNotFoundException extends RuntimeException{
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index f1a0d4e233b..b2995d48aa0 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -10,7 +10,7 @@
public class Tag {
public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}-.][\\p{Alnum}- .]*";
public final String 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..3ea73a9212c 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -7,10 +7,14 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
/**
@@ -19,24 +23,87 @@
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("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@gmail.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"),
+ getEmergencyContactSet(
+ new EmergencyContact(
+ new Name("Sarah Lim"),
+ new Phone("98761234"),
+ new Relationship("Daughter")),
+ new EmergencyContact(
+ new Name("Peter Yeoh"),
+ new Phone("97645132"),
+ new Relationship("Son"))),
+ new Doctor(new DoctorName("Tan Wei Ming"), new Phone("99119919"), new Email("drtan@gmail.com")),
+ getTagSet("Mandarin-speaking", "hard of hearing")),
+ new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@yahoo.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"),
+ getEmergencyContactSet(
+ new EmergencyContact(
+ new Name("Kevin Goh"),
+ new Phone("98764123"),
+ new Relationship("Husband")),
+ new EmergencyContact(
+ new Name("Brad Goh"),
+ new Phone("98764142"),
+ new Relationship("Son")
+ )),
+ new Doctor(new DoctorName("Lim Heng Seng"), new Phone("80987123"), new Email("drlim@gmail.com")),
+ getTagSet("short-term patient")),
+ new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@hotmail.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"),
+ getEmergencyContactSet(
+ new EmergencyContact(
+ new Name("Haziq Bin Abudllah"),
+ new Phone("98763412"),
+ new Relationship("Brother")),
+ new EmergencyContact(
+ new Name("Ahmad Bin Ahman"),
+ new Phone("98763448"),
+ new Relationship("Grandson"))
+ ),
+ new Doctor(new DoctorName("Robert Lim"), new Phone("91919191"), new Email("robertlim@gmail.com")),
+ getTagSet("needs hearing aid")),
+ new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@hotmail.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"),
+ getEmergencyContactSet(
+ new EmergencyContact(new Name("Amanda Lee"), new Phone("98762341"), new Relationship("Cousin"))
+ ),
+ new Doctor(new DoctorName("Jessica Loh"), new Phone("99119919"), new Email("jloh@gmail.com")),
+ getTagSet("short-term residential address")),
+ new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@gmail.com"),
new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
+ getEmergencyContactSet(
+ new EmergencyContact(
+ new Name("Nurul Ain"),
+ new Phone("98761243"),
+ new Relationship("Daughter")),
+ new EmergencyContact(
+ new Name("Izzudin Aiman"),
+ new Phone("94673215"),
+ new Relationship("Son")
+ ),
+ new EmergencyContact(
+ new Name("Khairul Anwar"),
+ new Phone("94673185"),
+ new Relationship("Relative")
+ )
+ ), new Doctor(new DoctorName("Zhou Jie Lun"), new Phone("88888888"), new Email("zhoujl@hotmail.com")),
+ getTagSet("requires Malay translator")),
+ new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@yahoo.com"),
new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
+ getEmergencyContactSet(
+ new EmergencyContact(
+ new Name("Anjali Devi"),
+ new Phone("98763124"),
+ new Relationship("Daughter")),
+ new EmergencyContact(
+ new Name("Belle Choy"),
+ new Phone("98763187"),
+ new Relationship("Granddaughter"))
+ ),
+ new Doctor(new DoctorName("Ed Sheeran"), new Phone("95114320"), new Email("edsheeran@gmail.com")),
+ getTagSet("speech impaired"))
};
}
@@ -48,6 +115,13 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
return sampleAb;
}
+ /**
+ * Returns a EmergencyContact set containing the list of emergency contacts given.
+ */
+ public static Set getEmergencyContactSet(EmergencyContact... emergencyContacts) {
+ return Arrays.stream(emergencyContacts).collect(Collectors.toSet());
+ }
+
/**
* Returns a tag set containing the list of strings given.
*/
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java b/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java
new file mode 100644
index 00000000000..f96a29392a0
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java
@@ -0,0 +1,78 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.DoctorName;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Phone;
+
+/**
+ * Constructs a {@code JsonAdaptedDoctor} with the given person details.
+ */
+public class JsonAdaptedDoctor {
+ private final String doctorName;
+ private final String doctorPhone;
+ private final String doctorEmail;
+
+ /**
+ * Constructs a {@code JsonAdaptedDoctor} with the given {@code doctorName},
+ * {@code doctorPhone} and {@code doctorEmail}.
+ */
+ @JsonCreator
+ public JsonAdaptedDoctor(@JsonProperty("doctorName") String doctorName,
+ @JsonProperty("doctorPhone") String doctorPhone,
+ @JsonProperty("doctorEmail") String doctorEmail) {
+ this.doctorName = doctorName;
+ this.doctorPhone = doctorPhone;
+ this.doctorEmail = doctorEmail;
+ }
+
+ /**
+ * Converts a given {@code Doctor} into this class for Jackson use.
+ */
+ public JsonAdaptedDoctor(Doctor source) {
+ doctorName = source.getName().fullName;
+ doctorPhone = source.getPhone().value;
+ doctorEmail = source.getEmail().value;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Doctor object into the model's {@code Doctor} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
+ */
+ public Doctor toModelType() throws IllegalValueException {
+ if (doctorName == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ DoctorName.class.getSimpleName()));
+ }
+ if (!DoctorName.isValidName(doctorName)) {
+ throw new IllegalValueException(DoctorName.MESSAGE_CONSTRAINTS);
+ }
+ final DoctorName modelDoctorName = new DoctorName(doctorName);
+
+ if (doctorPhone == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(doctorPhone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ final Phone modelDoctorPhone = new Phone(doctorPhone);
+
+ if (doctorEmail == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ Email.class.getSimpleName()));
+ }
+ if (!Email.isValidEmail(doctorEmail)) {
+ throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
+ }
+
+ final Email modelDoctorEmail = new Email(doctorEmail);
+
+ return new Doctor(modelDoctorName, modelDoctorPhone, modelDoctorEmail);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEmergencyContact.java b/src/main/java/seedu/address/storage/JsonAdaptedEmergencyContact.java
new file mode 100644
index 00000000000..48b4a986b18
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedEmergencyContact.java
@@ -0,0 +1,79 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
+
+/**
+ * Jackson-friendly version of {@link EmergencyContact}.
+ */
+public class JsonAdaptedEmergencyContact {
+ private final String ecName;
+ private final String ecPhone;
+ private final String ecRelationship;
+ /**
+ * Constructs a {@code JsonAdaptedEmergencyContact} with the given {@code ecName},
+ * {@code ecPhone} and {@code ecRelationship}.
+ */
+ @JsonCreator
+ public JsonAdaptedEmergencyContact(@JsonProperty("ecName") String ecName,
+ @JsonProperty("ecPhone") String ecPhone,
+ @JsonProperty("ecRelationship") String ecRelationship) {
+ this.ecName = ecName;
+ this.ecPhone = ecPhone;
+ this.ecRelationship = ecRelationship;
+ }
+
+ /**
+ * Converts a given {@code Emergency Contact} into this class for Jackson use.
+ */
+ public JsonAdaptedEmergencyContact(EmergencyContact source) {
+ ecName = source.getName().fullName;
+ ecPhone = source.getPhone().value;
+ ecRelationship = source.getRelationship().relationship;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Emergency Contact object into the model's {@code EmergencyContact} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
+ */
+ public EmergencyContact toModelType() throws IllegalValueException {
+ if (ecName == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(ecName)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ final Name modelEcName = new Name(ecName);
+
+ if (ecPhone == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(ecPhone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ final Phone modelEcPhone = new Phone(ecPhone);
+
+ if (ecRelationship == null) {
+ throw new IllegalValueException(String.format(JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT,
+ Relationship.class.getSimpleName()));
+ }
+ if (!Relationship.isAlphanumericRelationship(ecRelationship)) {
+ throw new IllegalValueException(Relationship.ALPHANUMERIC_CONSTRAINTS);
+ }
+ if (!Relationship.isValidRelationship(ecRelationship)) {
+ throw new IllegalValueException(Relationship.RELATIONSHIP_TYPE_CONSTRAINTS);
+ }
+ final Relationship modelEcRelationship = new Relationship(ecRelationship);
+
+ return new EmergencyContact(modelEcName, modelEcPhone, modelEcRelationship);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..fa629045678 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -2,6 +2,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -11,7 +12,9 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Doctor;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -28,6 +31,8 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final List emergencyContacts = new ArrayList<>();
+ private final JsonAdaptedDoctor doctor;
private final List tags = new ArrayList<>();
/**
@@ -36,11 +41,17 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("emergencyContacts") List emergencyContacts,
+ @JsonProperty("doctor") JsonAdaptedDoctor doctor,
@JsonProperty("tags") List tags) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ if (emergencyContacts != null) {
+ this.emergencyContacts.addAll(emergencyContacts);
+ }
+ this.doctor = doctor;
if (tags != null) {
this.tags.addAll(tags);
}
@@ -54,6 +65,10 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ emergencyContacts.addAll(source.getEmergencyContacts().stream()
+ .map(JsonAdaptedEmergencyContact::new)
+ .collect(Collectors.toList()));
+ doctor = new JsonAdaptedDoctor(source.getDoctor());
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
@@ -65,6 +80,11 @@ public JsonAdaptedPerson(Person source) {
* @throws IllegalValueException if there were any data constraints violated in the adapted person.
*/
public Person toModelType() throws IllegalValueException {
+ final List personEmergencyContacts = new ArrayList<>();
+ for (JsonAdaptedEmergencyContact emergencyContact : emergencyContacts) {
+ personEmergencyContacts.add(emergencyContact.toModelType());
+ }
+
final List personTags = new ArrayList<>();
for (JsonAdaptedTag tag : tags) {
personTags.add(tag.toModelType());
@@ -102,8 +122,20 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ if (emergencyContacts.isEmpty()) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ EmergencyContact.class.getSimpleName()));
+ }
+ final Set modelEmergencyContacts = new LinkedHashSet<>(personEmergencyContacts);
+
+ if (doctor == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Doctor"));
+ }
+ final Doctor modelDoctor = doctor.toModelType();
+
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelEmergencyContacts, modelDoctor,
+ modelTags);
}
}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
index 9e75478664b..82a0b942413 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/seedu/address/ui/CommandBox.java
@@ -1,85 +1,595 @@
package seedu.address.ui;
-import javafx.collections.ObservableList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
import javafx.fxml.FXML;
+import javafx.scene.control.Label;
import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
import javafx.scene.layout.Region;
-import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+
/**
- * The UI component that is responsible for receiving user command inputs.
+ * A CommandBox component that is part of the UI, allowing the user to input commands.
+ * This version provides simple word-by-word autocomplete suggestions based on the command syntax map.
*/
public class CommandBox extends UiPart {
-
public static final String ERROR_STYLE_CLASS = "error";
private static final String FXML = "CommandBox.fxml";
-
- private final CommandExecutor commandExecutor;
+ private static final Map commandSyntaxMap = new HashMap<>();
+ private static final Map> commandParameterOrder = new HashMap<>();
+ private static final Map parameterMap = new HashMap<>();
+ private static final String DEFAULT_STYLE = "-fx-font-family: 'Segoe UI'; -fx-font-size: 13pt;"
+ + " -fx-text-fill: white;";
+ private static final String INPUT_NEEDED_STYLE = "-fx-font-family: 'Segoe UI'; -fx-font-size: 13pt;"
+ + " -fx-text-fill: #fb5252;";
@FXML
private TextField commandTextField;
+ @FXML
+ private Label suggestionLabel;
+
+ private final CommandExecutor commandExecutor;
+
+ static {
+ // Initialize command syntax map
+ commandSyntaxMap.put("add", "add n/NAME p/PHONE e/EMAIL a/ADDRESS ecname/EMERGENCY_CONTACT_NAME "
+ + "ecphone/EMERGENCY_CONTACT_PHONE ecrs/EMERGENCY_CONTACT_RELATIONSHIP "
+ + "dname/DOCTOR_NAME dphone/DOCTOR_PHONE demail/DOCTOR_EMAIL t/TAG");
+ commandSyntaxMap.put("addec", "addec INDEX ecname/EMERGENCY_CONTACT_NAME ecphone/EMERGENCY_CONTACT_PHONE "
+ + "ecrs/EMERGENCY_CONTACT_RELATIONSHIP");
+ commandSyntaxMap.put("archive", "archive DESCRIPTION");
+ commandSyntaxMap.put("clear", "clear");
+ commandSyntaxMap.put("delete", "delete INDEX ec/EMERGENCY_CONTACT_INDEX");
+ commandSyntaxMap.put("edit", "edit INDEX n/NAME p/PHONE e/EMAIL a/ADDRESS ec/EMERGENCY_CONTACT_INDEX "
+ + "ecname/EMERGENCY_CONTACT_NAME ecrs/EMERGENCY_CONTACT_RELATIONSHIP dname/DOCTOR_NAME "
+ + "dphone/DOCTOR_PHONE demail/DOCTOR_EMAIL t/TAG");
+ commandSyntaxMap.put("find", "find KEYWORD MORE_KEYWORDS");
+ commandSyntaxMap.put("finddoc", "finddoc KEYWORD MORE_KEYWORDS");
+ commandSyntaxMap.put("help", "help");
+ commandSyntaxMap.put("list", "list SORT_ORDER");
+ commandSyntaxMap.put("listarchives", "listarchives");
+ commandSyntaxMap.put("loadarchive", "loadarchive FILE_NAME");
+ commandSyntaxMap.put("deletearchive", "deletearchive FILE_NAME");
+ commandSyntaxMap.put("redo", "redo");
+ commandSyntaxMap.put("undo", "undo");
+
+ // Initialize parameter mappings
+ parameterMap.put("n/", "n/NAME");
+ parameterMap.put("p/", "p/PHONE");
+ parameterMap.put("e/", "e/EMAIL");
+ parameterMap.put("a/", "a/ADDRESS");
+ parameterMap.put("ecname/", "ecname/EMERGENCY_CONTACT_NAME");
+ parameterMap.put("ecphone/", "ecphone/EMERGENCY_CONTACT_PHONE");
+ parameterMap.put("ecrs/", "ecrs/EMERGENCY_CONTACT_RELATIONSHIP");
+ parameterMap.put("dname/", "dname/DOCTOR_NAME");
+ parameterMap.put("dphone/", "dphone/DOCTOR_PHONE");
+ parameterMap.put("demail/", "demail/DOCTOR_EMAIL");
+ parameterMap.put("t/", "t/TAG");
+ parameterMap.put("ec/", "ec/EMERGENCY_CONTACT_INDEX");
+
+ // Initialize command-specific parameter order for commands that have "/" in them
+ commandParameterOrder.put("add", Arrays.asList(
+ "n/", "p/", "e/", "a/", "ecname/", "ecphone/", "ecrs/", "dname/", "dphone/", "demail/", "t/"
+ ));
+ commandParameterOrder.put("addec", Arrays.asList(
+ "ecname/", "ecphone/", "ecrs/"
+ ));
+ commandParameterOrder.put("edit", Arrays.asList(
+ "n/", "p/", "e/", "a/", "ec/", "ecname/", "ecrs/", "dname/", "dphone/", "demail/", "t/"
+ ));
+ commandParameterOrder.put("delete", Arrays.asList(
+ "ec/"
+ ));
+ }
/**
- * Creates a {@code CommandBox} with the given {@code CommandExecutor}.
+ * Creates a CommandBox with the given CommandExecutor.
+ * This command box provides an command interface with suggestions based on command syntax.
+ * Suggestions are shown for:
+ * - Available commands when typing command keywords
+ * - Parameter syntax for commands with parameters
+ * - Next parameter in sequence after completing each parameter
+ *
+ * @param commandExecutor Lambda function that executes the entered command text
*/
public CommandBox(CommandExecutor commandExecutor) {
super(FXML);
this.commandExecutor = commandExecutor;
- // calls #setStyleToDefault() whenever there is a change to the text of the command box.
- commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+ // Listen for ANY text changes to reset ALL styles
+ commandTextField.textProperty().addListener((observable, oldValue, newValue) -> {
+ resetStyle();
+ commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
+ handleTextChanged(newValue);
+ });
+
+ // Consume control key to autocomplete
+ commandTextField.setOnKeyPressed(event -> {
+ if (event.getCode() == KeyCode.CONTROL) {
+ handleControlPressed();
+ event.consume();
+ }
+ });
+ }
+ private void resetStyle() {
+ commandTextField.setStyle(DEFAULT_STYLE);
+ }
+
+
+ private String findShortestCommandMatch(String partial) {
+ if (partial.isEmpty()) {
+ return null;
+ }
+ String shortestMatch = null;
+
+ for (String command : commandSyntaxMap.keySet()) {
+ if (command.startsWith(partial)) {
+ if (shortestMatch == null || command.length() < shortestMatch.length()) {
+ shortestMatch = command;
+ }
+ }
+ }
+ return shortestMatch;
+ }
+
+ private void suggestNextParameter(String command, String input) {
+ String nextParam = getNextParameter(command, input.trim());
+ if (nextParam != null) {
+ String nextPrefix = nextParam.substring(0, nextParam.indexOf('/') + 1);
+ commandTextField.setText(input.trim() + " " + nextPrefix);
+ commandTextField.positionCaret(commandTextField.getText().length());
+ }
}
/**
- * Handles the Enter button pressed event.
+ * Retrieves the next uncompleted parameter for the given command based on the current input.
+ * The method checks the sequence of parameters defined for the command and returns the
+ * next parameter that has not been provided yet.
+ *
+ * Example:
+ * If the command is "add" and the currentInput is "add n/John p/12345",
+ * and the parameter sequence is ["n/", "p/", "e/"],
+ * this method will return "e/" since it is the next uncompleted parameter.
*/
+ private String getNextParameter(String command, String currentInput) {
+ List paramOrder = commandParameterOrder.get(command);
+ if (paramOrder == null) {
+ return null;
+ }
+
+ // Create a set of completed prefixes
+ Set completedPrefixes = new HashSet<>();
+
+ // Split input into parts and process each part in order
+ String[] parts = currentInput.split("\\s+");
+ for (int i = 1; i < parts.length; i++) {
+ String part = parts[i];
+ // Find the longest matching prefix for this part
+ String matchedPrefix = null;
+ for (String prefix : paramOrder) {
+ if (part.startsWith(prefix) && (matchedPrefix == null || prefix.length() > matchedPrefix.length())) {
+ matchedPrefix = prefix;
+ }
+ }
+
+ // If we found a prefix and there's a value after it, mark it as completed
+ if (matchedPrefix != null) {
+ String value = part.substring(matchedPrefix.length());
+ if (!value.isEmpty()) {
+ completedPrefixes.add(matchedPrefix);
+ }
+ }
+ }
+
+ // Find the first uncompleted parameter in the order
+ for (String prefix : paramOrder) {
+ if (!completedPrefixes.contains(prefix)) {
+ return parameterMap.get(prefix);
+ }
+ }
+
+ return null;
+ }
+
+
+ private void handleControlPressed() {
+ /* Trim excess spaces but keep the input intact
+ "\\s+$" is the REGEX pattern being used here:
+ '\\s' matches any whitespace character (including spaces, tabs, etc.).
+ '+' means one or more occurrences of the previous pattern (\\s).
+ '$' matches the end of the string.
+ */
+ String input = commandTextField.getText().replaceAll("\\s+$", ""); // Remove trailing spaces
+
+ // check for single word commands e.g(add, INDEX, find etc)
+ if (!input.contains(" ")) {
+ String match = findShortestCommandMatch(input.trim());
+ if (match != null) {
+ if (match.equals(input)) {
+ String syntax = commandSyntaxMap.get(match);
+ // these words need be replaced by 1-word inputs by users that cannot be autocompleted
+ if (syntax.contains("INDEX") || syntax.contains("KEYWORD")) {
+ commandTextField.setStyle(INPUT_NEEDED_STYLE);
+ return;
+ }
+ // If no INDEX needed, proceed with parameter autocomplete e.g.(proceed to n/ p/ cases)
+ suggestNextParameter(match, match); //input vs cur state same
+ } else {
+ // Just complete the command (delete, add, find)
+ commandTextField.setText(match);
+ commandTextField.positionCaret(match.length());
+ }
+ }
+ return;
+ }
+
+ // Check if input sequence is valid before proceeding with ANY autocomplete
+ if (!isValidPreSlashSequence(input)) {
+ return;
+ }
+
+ String[] parts = input.split("\\s+");
+ String command = parts[0];
+ String lastPart = parts[parts.length - 1];
+
+ // If last part ends with a slash, turn red (user needs to input info)
+ if (lastPart.endsWith("/")) {
+ commandTextField.setStyle(INPUT_NEEDED_STYLE);
+ return;
+ }
+
+ // Check if the last part contains a valid prefix and value
+ if (lastPart.contains("/")) {
+ String value = lastPart.substring(lastPart.indexOf('/') + 1).trim();
+
+ // If there's a value after the slash
+ if (!value.isEmpty()) {
+ suggestNextParameter(command, input);
+ return;
+ }
+ }
+
+ if (!commandSyntaxMap.containsKey(command)) {
+ return;
+ }
+ suggestNextParameter(command, input);
+ }
+
+ private boolean isValidPreSlashSequence(String input) {
+ String command = input.split("\\s+")[0];
+ if (!commandSyntaxMap.containsKey(command)) {
+ return false;
+ }
+
+ String fullSyntax = commandSyntaxMap.get(command);
+
+ if (fullSyntax.contains("INDEX") || fullSyntax.contains("KEYWORD")) {
+ String[] inputParts = input.trim().split("\\s+");
+ if (inputParts.length > 1) {
+ if (fullSyntax.contains("INDEX")) {
+ fullSyntax = fullSyntax.replace("INDEX", inputParts[1]);
+ } else {
+ fullSyntax = fullSyntax.replace("KEYWORD", inputParts[1]);
+ }
+ }
+ }
+
+ // Find the longest matching prefix in the input
+ String longestPrefix = null;
+ int longestPrefixLength = 0;
+ for (String prefix : parameterMap.keySet()) {
+ if (input.contains(prefix) && prefix.length() > longestPrefixLength) {
+ longestPrefix = prefix;
+ longestPrefixLength = prefix.length();
+ }
+ }
+
+ // If no prefix found or input shorter than what we found so far
+ if (longestPrefix == null) {
+ return true; // Allow typing to continue
+ }
+
+ int inputSlashPos = input.indexOf('/');
+ int syntaxSlashPos = fullSyntax.indexOf('/');
+
+ String inputToCompare = (inputSlashPos == -1) ? input : input.substring(0, inputSlashPos);
+ String syntaxToCompare = fullSyntax.substring(0, syntaxSlashPos);
+
+ if (inputToCompare.length() > syntaxToCompare.length()) {
+ return false;
+ }
+
+ for (int i = 0; i < inputToCompare.length(); i++) {
+ if (inputToCompare.charAt(i) != syntaxToCompare.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean hasInvalidParameterStructure(String input) {
+ String[] parts = input.split("\\s+");
+
+ // Create a set of valid prefixes for easier lookup
+ Set validPrefixes = new HashSet<>(parameterMap.keySet());
+
+ String currentPrefix = null; // Track current param prefix
+
+ for (String part : parts) {
+ if (part.trim().isEmpty()) {
+ continue;
+ }
+
+ int slashIndex = part.indexOf('/');
+ if (slashIndex != -1) {
+ // Check for space before slash in this part
+ if (part.substring(0, slashIndex).contains(" ")) {
+ return true; // Invalid if there's space before slash
+ }
+
+ // Find the longest valid prefix at the start of this part
+ String foundPrefix = null;
+ int longestLength = 0;
+
+ for (String prefix : validPrefixes) {
+ if (part.startsWith(prefix) && prefix.length() > longestLength) {
+ foundPrefix = prefix;
+ longestLength = prefix.length();
+ }
+ }
+
+ // If we found a valid prefix at the start
+ if (foundPrefix != null) {
+ currentPrefix = foundPrefix;
+ // Don't check for more slashes in the value part
+ } else if (currentPrefix == null) {
+ // Only invalid if we're not in a parameter value
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasProperSpaceBeforePrefix(String input, String prefix) {
+ int prefixPos = input.lastIndexOf(prefix);
+ if (prefixPos <= 0) {
+ return false;
+ }
+
+ // Get the last space before this prefix
+ int lastSpacePos = input.lastIndexOf(' ', prefixPos);
+ if (lastSpacePos == -1) {
+ return false; // No space found before prefix at all
+ }
+
+ // Get the text between the last space and this prefix
+ String textBetween = input.substring(lastSpacePos + 1, prefixPos);
+
+ // Check if there are any valid prefixes in the text between
+ // This handles cases like "namep/" or "phonee/"
+ for (String validPrefix : parameterMap.keySet()) {
+ if (textBetween.contains(validPrefix)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ private boolean isValidCommandPrefix(String input) {
+ // Split the input by spaces to isolate the first word (the command keyword)
+ String[] parts = input.trim().split("\\s+");
+ String commandPrefix = parts[0];
+ if (parts.length == 1) {
+ // Show suggestions if commandPrefix exactly matches any command
+ // or is a valid beginning of a command in the syntax map
+ for (String command : commandSyntaxMap.keySet()) {
+ if (command.startsWith(commandPrefix)) {
+ return true;
+ }
+ }
+ }
+
+ // Check if the commandPrefix exactly matches any valid command in the syntax map
+ return commandSyntaxMap.containsKey(commandPrefix);
+ }
+
+ private void handleTextChanged(String input) {
+
+ if (!isValidCommandPrefix(input)) {
+ suggestionLabel.setVisible(false);
+ return;
+ }
+ if (input.trim().isEmpty()) {
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ // Check if the last part of the input contains a slash
+ String[] parts = input.split("\\s+");
+ String lastPart = parts[parts.length - 1];
+
+ // For just the command (before any parameters)
+ if (!input.contains(" ")) {
+ StringBuilder suggestions = new StringBuilder();
+ boolean foundMatch = false;
+
+ List commands = new ArrayList<>(commandSyntaxMap.keySet());
+ Collections.sort(commands);
+
+ for (String command : commands) {
+ if (command.startsWith(input.trim())) {
+ if (foundMatch) {
+ suggestions.append(" | ");
+ }
+ suggestions.append(commandSyntaxMap.get(command));
+ foundMatch = true;
+ }
+ }
+
+ if (foundMatch) {
+ suggestionLabel.setText(suggestions.toString());
+ suggestionLabel.setVisible(true);
+ return;
+ }
+ }
+
+ String command = parts[0];
+
+ // Special handling for commands without slash parameters
+ if (!commandSyntaxMap.get(command).contains("/")) {
+ // If just the command is typed
+ if (input.trim().equals(command)) {
+ suggestionLabel.setText(commandSyntaxMap.get(command));
+ suggestionLabel.setVisible(true);
+ return;
+ }
+
+ // For list command with optional sort order
+ if (command.equals("list") && parts.length == 1) {
+ suggestionLabel.setText(commandSyntaxMap.get(command));
+ suggestionLabel.setVisible(true);
+ return;
+ }
+
+ // Hide suggestions once parameters are being typed for non-slash commands
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ // Regular handling for slash-parameter commands
+ if (hasInvalidParameterStructure(input)) {
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ if (!isValidPreSlashSequence(input)) {
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ // Find the last parameter prefix in the input with longest match
+ String longestPrefix = null;
+ int lastPrefixPos = -1;
+ int longestPrefixLength = 0;
+ String restOfInput = "";
+
+ for (String prefix : parameterMap.keySet()) {
+ int pos = input.lastIndexOf(prefix);
+ if (pos > -1 && hasProperSpaceBeforePrefix(input, prefix) && prefix.length() > longestPrefixLength) {
+ lastPrefixPos = pos;
+ longestPrefix = prefix;
+ longestPrefixLength = prefix.length();
+ restOfInput = input.substring(pos + prefix.length());
+ }
+ }
+
+ // We just typed the command
+ if (input.trim().equals(command)) {
+ suggestionLabel.setText(commandSyntaxMap.get(command));
+ suggestionLabel.setVisible(true);
+ return;
+ }
+
+ // If no valid prefix with proper spacing found, hide suggestion
+ if (longestPrefix == null) {
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ // If we just typed a properly spaced prefix OR if last part contains a slash
+ if (restOfInput.trim().isEmpty() || lastPart.contains("/")) {
+ String paramValue;
+ String prefix;
+ String beforePrefix;
+
+ if (lastPart.contains("/")) {
+ // For parts that contain a slash
+ prefix = lastPart.substring(0, lastPart.indexOf('/') + 1);
+ String valueAfterSlash = lastPart.substring(lastPart.indexOf('/') + 1);
+
+ // If typing after slash, hide suggestion unless followed by space
+ if (!valueAfterSlash.isEmpty()) {
+ if (input.endsWith(" ") && !valueAfterSlash.endsWith(" ")) {
+ // Show next parameter suggestion when space is hit
+ String nextParam = getNextParameter(command, input.trim());
+ if (nextParam != null) {
+ suggestionLabel.setText(input.trim() + " " + nextParam);
+ suggestionLabel.setVisible(true);
+ return;
+ }
+ }
+ suggestionLabel.setVisible(false);
+ return;
+ }
+
+ paramValue = parameterMap.get(prefix).substring(prefix.length());
+ beforePrefix = input.substring(0, input.lastIndexOf(lastPart));
+ } else {
+ // For just typed prefix
+ prefix = longestPrefix;
+ paramValue = parameterMap.get(longestPrefix).substring(longestPrefix.length());
+ beforePrefix = input.substring(0, lastPrefixPos);
+ }
+
+ suggestionLabel.setText(beforePrefix + prefix + paramValue);
+ suggestionLabel.setVisible(true);
+ return;
+ }
+
+ // If we just pressed space after completing a value
+ if (input.endsWith(" ")) {
+ String nextParam = getNextParameter(command, input.trim());
+ if (nextParam != null) {
+ suggestionLabel.setText(input.trim() + " " + nextParam);
+ suggestionLabel.setVisible(true);
+ return;
+ }
+ }
+
+ suggestionLabel.setVisible(false);
+ }
+
@FXML
private void handleCommandEntered() {
String commandText = commandTextField.getText();
- if (commandText.equals("")) {
+ if (commandText.isEmpty()) {
return;
}
try {
commandExecutor.execute(commandText);
commandTextField.setText("");
+ suggestionLabel.setVisible(false);
} catch (CommandException | ParseException e) {
setStyleToIndicateCommandFailure();
}
}
- /**
- * Sets the command box style to use the default style.
- */
- private void setStyleToDefault() {
- commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
- }
- /**
- * Sets the command box style to indicate a failed command.
- */
private void setStyleToIndicateCommandFailure() {
- ObservableList styleClass = commandTextField.getStyleClass();
-
- if (styleClass.contains(ERROR_STYLE_CLASS)) {
- return;
+ if (!commandTextField.getStyleClass().contains(ERROR_STYLE_CLASS)) {
+ commandTextField.getStyleClass().add(ERROR_STYLE_CLASS);
}
-
- styleClass.add(ERROR_STYLE_CLASS);
}
/**
- * Represents a function that can execute commands.
+ * Functional interface for command execution in the CommandBox.
+ * Implementations should handle parsing and execution of command text entered by users.
+ * Commands can throw CommandException for execution errors or ParseException for invalid syntax.
*/
@FunctionalInterface
public interface CommandExecutor {
- /**
- * Executes the command and returns the result.
- *
- * @see seedu.address.logic.Logic#execute(String)
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
+ void execute(String commandText) throws CommandException, ParseException;
}
-
}
diff --git a/src/main/java/seedu/address/ui/EmergencyContactCard.java b/src/main/java/seedu/address/ui/EmergencyContactCard.java
new file mode 100644
index 00000000000..a674292be52
--- /dev/null
+++ b/src/main/java/seedu/address/ui/EmergencyContactCard.java
@@ -0,0 +1,50 @@
+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.person.EmergencyContact;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class EmergencyContactCard extends UiPart {
+
+ private static final String FXML = "EmergencyContactListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final EmergencyContact emergencyContact;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label emergencyContactName;
+ @FXML
+ private Label id;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label emergencyContactPhone;
+ @FXML
+ private Label emergencyContactRelationship;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public EmergencyContactCard(EmergencyContact emergencyContact, int displayedIndex) {
+ super(FXML);
+ this.emergencyContact = emergencyContact;
+ id.setText(displayedIndex + ". ");
+ emergencyContactName.setText(emergencyContact.getName().fullName);
+ emergencyContactPhone.setText(emergencyContact.getPhone().value);
+ emergencyContactRelationship.setText(emergencyContact.getRelationship().relationship);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/EmergencyContactListPanel.java b/src/main/java/seedu/address/ui/EmergencyContactListPanel.java
new file mode 100644
index 00000000000..0c8f30b900b
--- /dev/null
+++ b/src/main/java/seedu/address/ui/EmergencyContactListPanel.java
@@ -0,0 +1,53 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.person.EmergencyContact;
+
+/**
+ * Panel containing the list of emergency contacts.
+ */
+public class EmergencyContactListPanel extends UiPart {
+ private static final String FXML = "EmergencyContactListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(EmergencyContactListPanel.class);
+
+ @FXML
+ private ListView emergencyContactListView;
+
+ /**
+ * Creates a {@code EmergencyContactListPanel} with the given {@code ObservableList}.
+ */
+ public EmergencyContactListPanel(ObservableList emergencyContacts) {
+ super(FXML);
+ emergencyContactListView.setItems(emergencyContacts);
+ emergencyContactListView.setCellFactory(listView -> new EmergencyContactListViewCell());
+ }
+
+ public ListView getEmergencyContactListView() {
+ return emergencyContactListView;
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code EmergencyContact}
+ * using a {@code EmergencyContactCard}.
+ */
+ class EmergencyContactListViewCell extends ListCell {
+ @Override
+ protected void updateItem(EmergencyContact emergencyContact, boolean empty) {
+ super.updateItem(emergencyContact, empty);
+
+ if (empty || emergencyContact == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new EmergencyContactCard(emergencyContact, getIndex() + 1).getRoot());
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/EmergencyContactSelectionController.java b/src/main/java/seedu/address/ui/EmergencyContactSelectionController.java
new file mode 100644
index 00000000000..b541a1af545
--- /dev/null
+++ b/src/main/java/seedu/address/ui/EmergencyContactSelectionController.java
@@ -0,0 +1,62 @@
+package seedu.address.ui;
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.scene.control.ListView;
+import javafx.scene.control.SelectionMode;
+import seedu.address.model.person.EmergencyContact;
+
+/**
+ * Manages selection behavior across multiple {@code ListView} instances
+ * to ensure only one {@code EmergencyContact} is selected at a time.
+ */
+public class EmergencyContactSelectionController {
+ private final List> emergencyContactListViews = new ArrayList<>();
+
+ /**
+ * Adds a {@code ListView} to the controller and sets up
+ * its selection listener to manage exclusive selection across all registered
+ * {@code ListView} instances.
+ *
+ * @param emergencyContactListView The {@code ListView} to add.
+ */
+ public void addEmergencyContactListView(ListView emergencyContactListView) {
+ emergencyContactListViews.add(emergencyContactListView);
+ setupEmergencyContactSelectionListener(emergencyContactListView);
+ }
+
+ /**
+ * Removes a {@code ListView} from the controller
+ *
+ * @param emergencyContactListView The {@code ListView} to remove.
+ */
+ public void removeEmergencyContactListView(ListView emergencyContactListView) {
+ emergencyContactListViews.remove(emergencyContactListView);
+ }
+
+ /**
+ * Sets up a selection listener on a given {@code ListView} to clear
+ * selections in other {@code ListView} instances when an item
+ * is selected in this list view.
+ *
+ * @param emergencyContactListView The {@code ListView} to set up the listener for.
+ */
+ private void setupEmergencyContactSelectionListener(ListView emergencyContactListView) {
+ emergencyContactListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
+
+ emergencyContactListView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
+ if (newVal != null) {
+ // Clear selection in other ListViews
+ clearListViewSelections(emergencyContactListView);
+ }
+ });
+ }
+
+ private void clearListViewSelections(ListView emergencyContactListView) {
+ for (ListView otherEmergencyContactListView : emergencyContactListViews) {
+ if (otherEmergencyContactListView != emergencyContactListView) {
+ otherEmergencyContactListView.getSelectionModel().clearSelection();
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..c0daf069405 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2425s1-cs2103t-t13-1.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..f71b6dbc71b 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -2,11 +2,19 @@
import java.util.Comparator;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
+import javafx.scene.control.ListView;
+import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Person;
/**
@@ -15,7 +23,8 @@
public class PersonCard extends UiPart {
private static final String FXML = "PersonListCard.fxml";
-
+ private static final double EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT = 90;
+ private static final double EMERGENCY_CONTACT_LIST_DEFAULT_BORDER_SIZE = 1.5;
/**
* Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
* As a consequence, UI elements' variable names cannot be set to such keywords
@@ -25,9 +34,9 @@ public class PersonCard extends UiPart {
*/
public final Person person;
-
@FXML
private HBox cardPane;
+ private EmergencyContactListPanel emergencyContactListPanel;
@FXML
private Label name;
@FXML
@@ -39,7 +48,19 @@ public class PersonCard extends UiPart {
@FXML
private Label email;
@FXML
+ private StackPane emergencyContactListPanelPlaceholder;
+ @FXML
+ private Label doctorName;
+ @FXML
+ private Label doctorPhone;
+ @FXML
+ private Label doctorEmail;
+ @FXML
private FlowPane tags;
+ @FXML
+ private VBox emergencyContactsBox;
+ @FXML
+ private VBox doctorBox;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -48,12 +69,87 @@ public PersonCard(Person person, int displayedIndex) {
super(FXML);
this.person = person;
id.setText(displayedIndex + ". ");
+ setupPersonCardText();
+ setupCardColours(displayedIndex);
+
+ emergencyContactListPanel = new EmergencyContactListPanel(
+ FXCollections.observableArrayList(person.getEmergencyContacts()));
+
+ ListView emergencyContactListView = emergencyContactListPanel.getEmergencyContactListView();
+ ChangeListener emergencyContactListener = emergencyContactSelectionListener();
+ MultipleSelectionModel emergencyContactSelectionModel =
+ emergencyContactListView.getSelectionModel();
+ emergencyContactSelectionModel.selectedItemProperty().addListener(emergencyContactListener);
+
+ emergencyContactListPanelPlaceholder.getChildren().add(emergencyContactListPanel.getRoot());
+ setupEmergencyContactListPanelPlaceholder();
+ }
+
+ /**
+ * Populates the {@code PersonCard} with {@code Person} details.
+ */
+ private void setupPersonCardText() {
name.setText(person.getName().fullName);
phone.setText(person.getPhone().value);
address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
+ doctorName.setText(person.getDoctor().getName().getDoctorName());
+ doctorPhone.setText(person.getDoctor().getPhone().value);
+ doctorEmail.setText(person.getDoctor().getEmail().value);
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
.forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
}
+
+ /**
+ * Sets the background colours of Doctor and Emergency Contact boxes to colours that alternate with
+ * {@code PersonCard} background colours.
+ */
+ private void setupCardColours(int displayedIndex) {
+ // Follows syntax of personCard, odd here refers to a zero-indexed list
+ if (displayedIndex % 2 == 0) {
+ doctorBox.getStyleClass().add("alternateColourListView-odd");
+ emergencyContactsBox.getStyleClass().add("alternateColourListView-odd");
+ } else {
+ doctorBox.getStyleClass().add("alternateColourListView-even");
+ emergencyContactsBox.getStyleClass().add("alternateColourListView-even");
+ }
+ }
+
+ /**
+ * Sets the height constraints of the {@code emergencyContactListPanelPlaceholder} with respect to the number of
+ * emergency contacts within the panel.
+ */
+ private void setupEmergencyContactListPanelPlaceholder() {
+ int numEmergencyContacts = person.getEmergencyContacts().size();
+ emergencyContactListPanelPlaceholder.setPrefHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT
+ * numEmergencyContacts);
+ emergencyContactListPanelPlaceholder.setMaxHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT * 2);
+ }
+
+ /**
+ * Changes the height of the {@code emergencyContactListPanelPlaceholder} when the emergency contacts are selected,
+ * to accommodate increased {@code emergencyContactCard} heights, as a border is applied upon selection.
+ */
+ private ChangeListener emergencyContactSelectionListener() {
+ return (ObservableValue extends EmergencyContact> observableValue,
+ EmergencyContact previousSelection,
+ EmergencyContact currentSelection) -> {
+ int updatedNumEmergencyContacts = person.getEmergencyContacts().size();
+ if (currentSelection != null) {
+ emergencyContactListPanelPlaceholder.setPrefHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT
+ * updatedNumEmergencyContacts + EMERGENCY_CONTACT_LIST_DEFAULT_BORDER_SIZE * 2);
+ emergencyContactListPanelPlaceholder.setMaxHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT
+ * 2 + EMERGENCY_CONTACT_LIST_DEFAULT_BORDER_SIZE * 2);
+ } else {
+ emergencyContactListPanelPlaceholder.setPrefHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT
+ * updatedNumEmergencyContacts);
+ emergencyContactListPanelPlaceholder.setMaxHeight(EMERGENCY_CONTACT_LIST_DEFAULT_CARD_HEIGHT * 2);
+ }
+ };
+ }
+
+ public ListView getEmergencyContactListView() {
+ return emergencyContactListPanel.getEmergencyContactListView();
+ }
}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..3aaff8150fb 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -16,7 +16,7 @@
public class PersonListPanel extends UiPart {
private static final String FXML = "PersonListPanel.fxml";
private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
-
+ private final EmergencyContactSelectionController emergencyContactSelectionController;
@FXML
private ListView personListView;
@@ -25,6 +25,7 @@ public class PersonListPanel extends UiPart {
*/
public PersonListPanel(ObservableList personList) {
super(FXML);
+ emergencyContactSelectionController = new EmergencyContactSelectionController();
personListView.setItems(personList);
personListView.setCellFactory(listView -> new PersonListViewCell());
}
@@ -33,15 +34,24 @@ public PersonListPanel(ObservableList personList) {
* Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
*/
class PersonListViewCell extends ListCell {
+ private PersonCard personCard;
@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (empty || person == null) {
+ if (personCard != null) {
+ emergencyContactSelectionController.removeEmergencyContactListView(
+ personCard.getEmergencyContactListView());
+ }
setGraphic(null);
setText(null);
+ personCard = null;
} else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
+ personCard = new PersonCard(person, getIndex() + 1);
+ setGraphic(personCard.getRoot());
+ emergencyContactSelectionController.addEmergencyContactListView(
+ personCard.getEmergencyContactListView());
}
}
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..2c3b993ac19 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/health-report.png";
private Logic logic;
private MainWindow mainWindow;
@@ -32,6 +32,26 @@ public UiManager(Logic logic) {
this.logic = logic;
}
+ /**
+ * Shows an alert dialog on {@code owner} with the given parameters.
+ * This method only returns after the user has closed the alert dialog.
+ */
+ private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText,
+ String contentText) {
+ final Alert alert = new Alert(type);
+ alert.getDialogPane().getStylesheets().add("view/DarkTheme.css");
+ alert.initOwner(owner);
+ alert.setTitle(title);
+ alert.setHeaderText(headerText);
+ alert.setContentText(contentText);
+ alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID);
+ alert.showAndWait();
+ }
+
+ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) {
+ showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText);
+ }
+
@Override
public void start(Stage primaryStage) {
logger.info("Starting UI...");
@@ -54,26 +74,6 @@ private Image getImage(String imagePath) {
return new Image(MainApp.class.getResourceAsStream(imagePath));
}
- void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) {
- showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText);
- }
-
- /**
- * Shows an alert dialog on {@code owner} with the given parameters.
- * This method only returns after the user has closed the alert dialog.
- */
- private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText,
- String contentText) {
- final Alert alert = new Alert(type);
- alert.getDialogPane().getStylesheets().add("view/DarkTheme.css");
- alert.initOwner(owner);
- alert.setTitle(title);
- alert.setHeaderText(headerText);
- alert.setContentText(contentText);
- alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID);
- alert.showAndWait();
- }
-
/**
* Shows an error alert dialog with {@code title} and error message, {@code e},
* and exits the application after the user has closed the alert dialog.
diff --git a/src/main/resources/images/address.png b/src/main/resources/images/address.png
new file mode 100644
index 00000000000..49aa15839db
Binary files /dev/null and b/src/main/resources/images/address.png differ
diff --git a/src/main/resources/images/doctor.png b/src/main/resources/images/doctor.png
new file mode 100644
index 00000000000..1ad945e7839
Binary files /dev/null and b/src/main/resources/images/doctor.png differ
diff --git a/src/main/resources/images/health-report.png b/src/main/resources/images/health-report.png
new file mode 100644
index 00000000000..4b839aba17e
Binary files /dev/null and b/src/main/resources/images/health-report.png differ
diff --git a/src/main/resources/images/mail.png b/src/main/resources/images/mail.png
new file mode 100644
index 00000000000..92814c10247
Binary files /dev/null and b/src/main/resources/images/mail.png differ
diff --git a/src/main/resources/images/phone.png b/src/main/resources/images/phone.png
new file mode 100644
index 00000000000..7dd9d60da22
Binary files /dev/null and b/src/main/resources/images/phone.png differ
diff --git a/src/main/resources/images/relationship.png b/src/main/resources/images/relationship.png
new file mode 100644
index 00000000000..ae84498658f
Binary files /dev/null and b/src/main/resources/images/relationship.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 124283a392e..9478b66e6cd 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -1,9 +1,17 @@
+
-
-
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..8332d5743ab 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -93,43 +93,86 @@
-fx-background-color: derive(#1d1d1d, 20%);
}
-.list-cell {
+#personListPanel .list-cell {
-fx-label-padding: 0 0 0 0;
-fx-graphic-text-gap : 0;
-fx-padding: 0 0 0 0;
}
-.list-cell:filled:even {
+#personListView .list-cell:filled:even {
-fx-background-color: #3c3e3f;
}
-.list-cell:filled:odd {
+#personListView .list-cell:filled:odd {
-fx-background-color: #515658;
}
-.list-cell:filled:selected {
+#personListView .list-cell:filled:selected {
-fx-background-color: #424d5f;
}
-.list-cell:filled:selected #cardPane {
+#personListView .list-cell:filled:selected #cardPane {
-fx-border-color: #3e7b91;
-fx-border-width: 1;
}
-.list-cell .label {
+.alternateColourListView-odd {
+ -fx-background-color: #3c3e3f;
+}
+
+.alternateColourListView-even {
+ -fx-background-color: #515658;
+}
+
+#emergencyContactListView .list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+#emergencyContactListView .list-cell:filled:even {
+ -fx-background-color: #50879B;
+}
+
+#emergencyContactListView .list-cell:filled:odd {
+ -fx-background-color: #6E9CAC;
+}
+
+#emergencyContactListView .list-cell:filled:selected {
+ -fx-background-color: #92B4C1;
+}
+
+#emergencyContactListView .list-cell:filled:selected #cardPane {
+ -fx-border-color: white;
+ -fx-border-width: 1.5;
+}
+
+#emergencyContactListView .list-cell .label {
-fx-text-fill: white;
}
.cell_big_label {
-fx-font-family: "Segoe UI Semibold";
-fx-font-size: 16px;
- -fx-text-fill: #010504;
+ -fx-text-fill: white;
}
.cell_small_label {
-fx-font-family: "Segoe UI";
-fx-font-size: 13px;
- -fx-text-fill: #010504;
+ -fx-text-fill: white;
+}
+
+.emergencyContact {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #9999ff;
+}
+
+.doctor {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #4ac6ff;
}
.stack-pane {
diff --git a/src/main/resources/view/EmergencyContactListCard.fxml b/src/main/resources/view/EmergencyContactListCard.fxml
new file mode 100644
index 00000000000..2cd2f6af17f
--- /dev/null
+++ b/src/main/resources/view/EmergencyContactListCard.fxml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/EmergencyContactListPanel.fxml b/src/main/resources/view/EmergencyContactListPanel.fxml
new file mode 100644
index 00000000000..7aa1171bd85
--- /dev/null
+++ b/src/main/resources/view/EmergencyContactListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..05df3b0c184 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -11,10 +11,10 @@
-
+
-
+
@@ -39,19 +39,21 @@
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index 84e09833a87..60d4758b61e 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -6,10 +6,13 @@
+
+
+
-
+
@@ -28,9 +31,71 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 01b691792a9..6044fe878ee 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -5,5 +5,5 @@
-
+
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..541d8785c2f 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,31 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
+ "emergencyContacts" : [{
+ "ecName" : "Kevin Goh",
+ "ecPhone" : "98764123",
+ "ecRelationship" : "Husband"
+ }],
+ "doctor": {
+ "doctorName": "Tan Wei Ming",
+ "doctorPhone": "82345678",
+ "doctorEmail": "tanweiming@gmail.com"
+ },
"tags": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
+ "emergencyContacts" : [{
+ "ecName" : "Kevin Goh",
+ "ecPhone" : "98764123",
+ "ecRelationship" : "Husband"
+ }],
+ "doctor": {
+ "doctorName": "Tan Wei Ming",
+ "doctorPhone": "82345678",
+ "doctorEmail": "tanweiming@gmail.com"
+ },
"address": "4th street"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..570f3878793 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,17 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
+ "address": "4th street",
+ "emergencyContacts" : [ {
+ "ecName" : "Kevin Goh",
+ "ecPhone" : "98764123",
+ "ecRelationship" : "Husband"
+ }],
+ "doctor": {
+ "doctorName": "Tan Wei Ming",
+ "doctorPhone": "82345678",
+ "doctorEmail": "tanweiming@gmail.com"
+ },
+ "tags": [ "friends" ]
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..0d6578c5040 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,116 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
+ "emergencyContacts" : [{
+ "ecName" : "Sarah Lim",
+ "ecPhone" : "98761234",
+ "ecRelationship" : "Parent"
+ }],
+ "doctor": {
+ "doctorName": "Tan Wei Ming",
+ "doctorPhone": "82345678",
+ "doctorEmail": "tanweiming@gmail.com"
+ },
"tags" : [ "friends" ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
+ "emergencyContacts" : [{
+ "ecName" : "Kevin Goh",
+ "ecPhone" : "98764123",
+ "ecRelationship" : "Husband"
+ }],
+ "doctor": {
+ "doctorName": "Koh Jia Jia",
+ "doctorPhone": "81818211",
+ "doctorEmail": "jjkoh@hotmail.com"
+ },
"tags" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
+ "emergencyContacts" : [{
+ "ecName" : "Kevin Goh",
+ "ecPhone" : "98764123",
+ "ecRelationship" : "Husband"
+ }],
+ "doctor": {
+ "doctorName": "Ben Po",
+ "doctorPhone": "61231231",
+ "doctorEmail": "benpo@gmail.com"
+ },
"tags" : [ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
+ "emergencyContacts" : [{
+ "ecName" : "Haziq Bin Abudllah",
+ "ecPhone" : "98763412",
+ "ecRelationship" : "Brother"
+ }],
+ "doctor": {
+ "doctorName": "Sebastian Sim",
+ "doctorPhone": "62353535",
+ "doctorEmail": "sebsim@hotmail.com"
+ },
"tags" : [ "friends" ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
+ "emergencyContacts" : [{
+ "ecName" : "Amanda Lee",
+ "ecPhone" : "98762341",
+ "ecRelationship" : "Cousin"
+ }],
+ "doctor": {
+ "doctorName": "Wong See Toh",
+ "doctorPhone": "88338483",
+ "doctorEmail": "wongst@hotmail.com"
+ },
"tags" : [ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
+ "emergencyContacts" : [{
+ "ecName" : "Nurul Ain",
+ "ecPhone" : "98761243",
+ "ecRelationship" : "Grandmother"
+ }],
+ "doctor": {
+ "doctorName": "Bing Qi Ling",
+ "doctorPhone": "81818211",
+ "doctorEmail": "bingqiling@hotmail.com"
+ },
"tags" : [ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
+ "emergencyContacts" : [{
+ "ecName" : "Anjali Devi",
+ "ecPhone" : "98763124",
+ "ecRelationship" : "Daughter"
+ }, {
+ "ecName" : "Buddy Hoagies",
+ "ecPhone" : "64815432",
+ "ecRelationship" : "Son"
+ }],
+ "doctor": {
+ "doctorName": "Peh Boon Hao",
+ "doctorPhone": "96161616",
+ "doctorEmail": "boonhao@hotmail.com"
+ },
"tags" : [ ]
} ]
}
diff --git a/src/test/java/seedu/address/commons/core/filename/FilenameTest.java b/src/test/java/seedu/address/commons/core/filename/FilenameTest.java
new file mode 100644
index 00000000000..e02069fd6e4
--- /dev/null
+++ b/src/test/java/seedu/address/commons/core/filename/FilenameTest.java
@@ -0,0 +1,60 @@
+package seedu.address.commons.core.filename;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class FilenameTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Filename(null));
+ }
+
+ @Test
+ public void constructor_invalidName_throwsIllegalArgumentException() {
+ String invalidFilename = "/";
+ assertThrows(IllegalArgumentException.class, () -> new Filename(invalidFilename));
+ }
+
+ @Test
+ public void isValidFilename() {
+ // null name
+ assertThrows(NullPointerException.class, () -> Filename.isValidFilename(null));
+
+ // invalid name
+ assertFalse(Filename.isValidFilename("/")); // forward slash
+ assertFalse(Filename.isValidFilename(" \\")); // backslash
+ assertFalse(Filename.isValidFilename("test?")); // question mark
+ assertFalse(Filename.isValidFilename("Test *")); // asterisk
+
+ // valid name
+ assertTrue(Filename.isValidFilename("peter jack")); // alphabets only
+ assertTrue(Filename.isValidFilename("12345")); // numbers only
+ assertTrue(Filename.isValidFilename("peter the 2nd")); // alphanumeric characters
+ assertTrue(Filename.isValidFilename("Capital Tan")); // with capital letters
+ assertTrue(Filename.isValidFilename("David Roger Jackson Ray Jr 2nd")); // long names
+ }
+
+ @Test
+ public void equals() {
+ Filename filename = new Filename("Valid Name");
+
+ // same values -> returns true
+ assertTrue(filename.equals(new Filename("Valid Name")));
+
+ // same object -> returns true
+ assertTrue(filename.equals(filename));
+
+ // null -> returns false
+ assertFalse(filename.equals(null));
+
+ // different types -> returns false
+ assertFalse(filename.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(filename.equals(new Filename("Other Valid Name")));
+ }
+}
diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java
index 594de1e6365..375dc727b59 100644
--- a/src/test/java/seedu/address/commons/util/AppUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java
@@ -9,7 +9,7 @@ public class AppUtilTest {
@Test
public void getImage_exitingImage() {
- assertNotNull(AppUtil.getImage("/images/address_book_32.png"));
+ assertNotNull(AppUtil.getImage("/images/health-report.png"));
}
@Test
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..458ab21fe64 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -109,7 +109,7 @@ public void containsWordIgnoreCase_validInputs_correctResult() {
assertFalse(StringUtil.containsWordIgnoreCase(" ", "123"));
// Matches a partial word only
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
+ assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word
// Matches word in the sentence, different upper/lower case letters
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..c4cf4047dcf 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -4,6 +4,12 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
@@ -166,7 +172,8 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
// Triggers the saveAddressBook method by executing an add command
String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + ECNAME_DESC_AMY + ECPHONE_DESC_AMY + ECRS_DESC_AMY
+ + DOC_NAME_DESC_AMY + DOC_PHONE_DESC_AMY + DOC_EMAIL_DESC_AMY;
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..a31821257fc 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -10,12 +10,14 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.commons.core.filename.Filename;
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
@@ -85,16 +87,16 @@ public void toStringMethod() {
}
/**
- * A default model stub that have all of the methods failing.
+ * A default model stub that have all the methods failing.
*/
private class ModelStub implements Model {
@Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ public ReadOnlyUserPrefs getUserPrefs() {
throw new AssertionError("This method should not be called.");
}
@Override
- public ReadOnlyUserPrefs getUserPrefs() {
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
throw new AssertionError("This method should not be called.");
}
@@ -119,12 +121,43 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
}
@Override
- public void addPerson(Person person) {
+ public Path getArchiveDirectoryPath() {
throw new AssertionError("This method should not be called.");
}
@Override
- public void setAddressBook(ReadOnlyAddressBook newData) {
+ public void archiveAddressBook(Filename filename) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canUndoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void saveAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
throw new AssertionError("This method should not be called.");
}
@@ -133,6 +166,11 @@ public ReadOnlyAddressBook getAddressBook() {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public boolean hasPerson(Person person) {
throw new AssertionError("This method should not be called.");
@@ -157,6 +195,11 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void sortFilteredPersonList(Comparator comparator) {
+ throw new AssertionError("This method should not be called.");
+ }
}
/**
@@ -180,7 +223,7 @@ public boolean hasPerson(Person person) {
/**
* A Model stub that always accept the person being added.
*/
- private class ModelStubAcceptingPersonAdded extends ModelStub {
+ public class ModelStubAcceptingPersonAdded extends ModelStub {
final ArrayList personsAdded = new ArrayList<>();
@Override
diff --git a/src/test/java/seedu/address/logic/commands/AddEmergencyContactCommandTest.java b/src/test/java/seedu/address/logic/commands/AddEmergencyContactCommandTest.java
new file mode 100644
index 00000000000..d2477807a1e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddEmergencyContactCommandTest.java
@@ -0,0 +1,141 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
+
+public class AddEmergencyContactCommandTest {
+ private static final EmergencyContact VALID_EMERGENCY_CONTACT = new EmergencyContact(
+ new Name(VALID_ECNAME_AMY), new Phone(VALID_ECPHONE_AMY), new Relationship(VALID_ECRS_AMY));
+ private static final EmergencyContact VALID_EMERGENCY_CONTACT_BOB = new EmergencyContact(
+ new Name(VALID_ECNAME_BOB), new Phone(VALID_ECPHONE_BOB), new Relationship(VALID_ECRS_BOB));
+ private static final Set ALICE_EMERGENCY_CONTACTS =
+ new LinkedHashSet<>(ALICE.getEmergencyContacts());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ private Person createExpectedPerson() {
+ Set emergencyContacts = new LinkedHashSet<>(ALICE_EMERGENCY_CONTACTS);
+ emergencyContacts.add(VALID_EMERGENCY_CONTACT);
+ return new Person(ALICE.getName(), ALICE.getPhone(), ALICE.getEmail(),
+ ALICE.getAddress(), emergencyContacts, ALICE.getDoctor(), ALICE.getTags());
+ }
+
+ @Test
+ public void constructor_nullIndexAndNullEmergencyContact_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddEmergencyContactCommand(null,
+ null));
+ }
+
+ @Test
+ public void execute_personAcceptedByModel_addSuccessful() throws Exception {
+
+ CommandResult commandResult = new AddEmergencyContactCommand(INDEX_FIRST_PERSON,
+ VALID_EMERGENCY_CONTACT).execute(model);
+
+ Person expectedPerson = createExpectedPerson();
+
+ assertEquals(String.format(AddEmergencyContactCommand.MESSAGE_SUCCESS,
+ Messages.format(expectedPerson)),
+ commandResult.getFeedbackToUser());
+ assertEquals(expectedPerson, model.getFilteredPersonList().get(0));
+ }
+
+ /**
+ * Edit filtered list where index is larger than size of filtered list,
+ * but smaller than size of address book
+ */
+ @Test
+ public void execute_invalidPersonIndexFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+
+ AddEmergencyContactCommand addEmergencyContactCommand = new AddEmergencyContactCommand(outOfBoundIndex,
+ VALID_EMERGENCY_CONTACT);
+
+ assertCommandFailure(addEmergencyContactCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void createEditedPerson_test() {
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Person actualPerson = AddEmergencyContactCommand.createEditedPerson(
+ model.getFilteredPersonList().get(0), VALID_EMERGENCY_CONTACT);
+ Person expectedPerson = createExpectedPerson();
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void duplicateEmergencyContact_test() {
+ EmergencyContact invalidEmergencyContact =
+ model.getFilteredPersonList().get(0).getEmergencyContact(Index.fromOneBased(1));
+ AddEmergencyContactCommand addEmergencyContactCommand = new AddEmergencyContactCommand(INDEX_FIRST_PERSON,
+ invalidEmergencyContact);
+ assertCommandFailure(addEmergencyContactCommand, model,
+ AddEmergencyContactCommand.MESSAGE_DUPLICATE_EMERGENCY_CONTACT);
+ }
+
+ @Test
+ public void equals() {
+ AddEmergencyContactCommand addAliceCommand =
+ new AddEmergencyContactCommand(INDEX_FIRST_PERSON, VALID_EMERGENCY_CONTACT);
+ AddEmergencyContactCommand addBobCommand = new AddEmergencyContactCommand(INDEX_FIRST_PERSON,
+ VALID_EMERGENCY_CONTACT_BOB);
+
+ // same object -> returns true
+ assertTrue(addAliceCommand.equals(addAliceCommand));
+
+ // same values -> returns true
+ AddEmergencyContactCommand addAliceCommandCopy = new AddEmergencyContactCommand(INDEX_FIRST_PERSON,
+ VALID_EMERGENCY_CONTACT);
+ assertTrue(addAliceCommand.equals(addAliceCommandCopy));
+
+ // different types -> returns false
+ assertFalse(addAliceCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(addAliceCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(addAliceCommand.equals(addBobCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ AddEmergencyContactCommand addCommand = new AddEmergencyContactCommand(INDEX_FIRST_PERSON,
+ VALID_EMERGENCY_CONTACT);
+ String expected = AddEmergencyContactCommand.class.getCanonicalName() + "{toAdd="
+ + VALID_EMERGENCY_CONTACT + "}";
+ assertEquals(expected, addCommand.toString());
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java b/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java
new file mode 100644
index 00000000000..11d09753ad1
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java
@@ -0,0 +1,98 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.io.IOException;
+import java.nio.file.Files;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class ArchiveCommandTest {
+ @BeforeEach
+ public void setUp() throws IOException {
+ FileUtil.createIfMissing(new ModelManager().getAddressBookFilePath());
+ }
+
+ @AfterEach
+ public void tearDown() throws IOException {
+ Files.deleteIfExists(new ModelManager().getAddressBookFilePath());
+
+ // Delete all files in the archive directory
+ Files.walk(new ModelManager().getAddressBookFilePath().getParent())
+ .map(java.nio.file.Path::toFile)
+ .forEach(file -> {
+ if (!file.delete()) {
+ file.deleteOnExit();
+ }
+ });
+ }
+
+ @Test
+ public void execute_emptyAddressBook_success() {
+ Model model = new ModelManager();
+ Model expectedModel = new ModelManager();
+
+ assertCommandSuccess(new ArchiveCommand(new Filename("")), model, ArchiveCommand.MESSAGE_SUCCESS,
+ expectedModel);
+ }
+
+ @Test
+ public void execute_nonEmptyAddressBook_success() {
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ assertCommandSuccess(new ArchiveCommand(new Filename("")), model, ArchiveCommand.MESSAGE_SUCCESS,
+ expectedModel);
+ }
+
+ @Test
+ public void execute_emptyAddressBook_failure() {
+ Model model = new ModelManagerStubThrowingIoException();
+
+ assertCommandFailure(new ArchiveCommand(new Filename("")), model, ArchiveCommand.MESSAGE_FAILURE);
+ }
+
+ @Test
+ public void equals() {
+ ArchiveCommand archiveFirstCommand = new ArchiveCommand(new Filename("test1"));
+ ArchiveCommand archiveSecondCommand = new ArchiveCommand(new Filename("test2"));
+
+ // same object -> returns true
+ assertTrue(archiveFirstCommand.equals(archiveFirstCommand));
+
+ // same values -> returns true
+ ArchiveCommand archiveFirstCommandCopy = new ArchiveCommand(new Filename("test1"));
+ assertTrue(archiveFirstCommand.equals(archiveFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(archiveFirstCommand.equals("test1"));
+
+ // null -> returns false
+ assertFalse(archiveFirstCommand.equals(null));
+
+ // different command -> returns false
+ assertFalse(archiveFirstCommand.equals(archiveSecondCommand));
+ }
+
+ /**
+ * A ModelManager stub that always throws IOException when archiveAddressBook() is called.
+ */
+ private static class ModelManagerStubThrowingIoException extends ModelManager {
+ @Override
+ public void archiveAddressBook(Filename filename) throws IOException {
+ throw new IOException();
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..985144826b4 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -3,7 +3,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
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;
@@ -34,6 +40,18 @@ public class CommandTestUtil {
public static final String VALID_EMAIL_BOB = "bob@example.com";
public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
+ public static final String VALID_ECNAME_AMY = "Lim Jun Kai";
+ public static final String VALID_ECNAME_BOB = "Henry Wong";
+ public static final String VALID_ECPHONE_AMY = "84651752";
+ public static final String VALID_ECPHONE_BOB = "78465912";
+ public static final String VALID_ECRS_AMY = "Sibling";
+ public static final String VALID_ECRS_BOB = "Cousin";
+ public static final String VALID_DOC_NAME_AMY = "Tan";
+ public static final String VALID_DOC_NAME_BOB = "Lee";
+ public static final String VALID_DOC_PHONE_AMY = "12345678";
+ public static final String VALID_DOC_PHONE_BOB = "87654321";
+ public static final String VALID_DOC_EMAIL_AMY = "tan@gmail.com";
+ public static final String VALID_DOC_EMAIL_BOB = "lee@gmail.com";
public static final String VALID_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
@@ -45,6 +63,18 @@ public class CommandTestUtil {
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
+ public static final String ECNAME_DESC_AMY = " " + PREFIX_EMERGENCY_CONTACT_NAME + VALID_ECNAME_AMY;
+ public static final String ECNAME_DESC_BOB = " " + PREFIX_EMERGENCY_CONTACT_NAME + VALID_ECNAME_BOB;
+ public static final String ECPHONE_DESC_AMY = " " + PREFIX_EMERGENCY_CONTACT_PHONE + VALID_ECPHONE_AMY;
+ public static final String ECPHONE_DESC_BOB = " " + PREFIX_EMERGENCY_CONTACT_PHONE + VALID_ECPHONE_BOB;
+ public static final String ECRS_DESC_AMY = " " + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + VALID_ECRS_AMY;
+ public static final String ECRS_DESC_BOB = " " + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + VALID_ECRS_BOB;
+ public static final String DOC_NAME_DESC_AMY = " " + PREFIX_DOC_NAME + VALID_DOC_NAME_AMY;
+ public static final String DOC_NAME_DESC_BOB = " " + PREFIX_DOC_NAME + VALID_DOC_NAME_BOB;
+ public static final String DOC_EMAIL_DESC_AMY = " " + PREFIX_DOC_EMAIL + VALID_DOC_EMAIL_AMY;
+ public static final String DOC_EMAIL_DESC_BOB = " " + PREFIX_DOC_EMAIL + VALID_DOC_EMAIL_BOB;
+ public static final String DOC_PHONE_DESC_AMY = " " + PREFIX_DOC_PHONE + VALID_DOC_PHONE_AMY;
+ public static final String DOC_PHONE_DESC_BOB = " " + PREFIX_DOC_PHONE + VALID_DOC_PHONE_BOB;
public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
@@ -52,6 +82,18 @@ public class CommandTestUtil {
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
+
+ // '&' not allowed in names
+ public static final String INVALID_ECNAME_DESC = " " + PREFIX_EMERGENCY_CONTACT_NAME + "James&";
+
+ // 'a' not allowed in phones
+ public static final String INVALID_ECPHONE_DESC = " " + PREFIX_EMERGENCY_CONTACT_PHONE + "911a";
+
+ // '*' not allowed in relationships
+ public static final String INVALID_ECRS_DESC = " " + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "hubby*";
+ public static final String INVALID_DOC_EMAIL_DESC = " " + PREFIX_DOC_EMAIL + "bob!yahoo"; // missing '@' symbol
+ public static final String INVALID_DOC_NAME_DESC = " " + PREFIX_DOC_NAME + "John*"; // '*' not allowed in names
+ public static final String INVALID_DOC_PHONE_DESC = " " + PREFIX_DOC_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
@@ -63,9 +105,16 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withEmergencyContactName(VALID_ECNAME_AMY).withEmergencyContactPhone(VALID_ECPHONE_AMY)
+ .withEmergencyContactRelationship(VALID_ECRS_AMY).withDoctorName(VALID_DOC_NAME_AMY)
+ .withDoctorPhone(VALID_DOC_PHONE_AMY).withDoctorEmail(VALID_DOC_EMAIL_AMY)
.withTags(VALID_TAG_FRIEND).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withEmergencyContactName(VALID_ECNAME_BOB).withEmergencyContactPhone(VALID_ECPHONE_BOB)
+ .withEmergencyContactRelationship(VALID_ECRS_BOB)
+ .withDoctorName(VALID_DOC_NAME_BOB).withDoctorPhone(VALID_DOC_PHONE_BOB)
+ .withDoctorEmail(VALID_DOC_EMAIL_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteArchiveCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteArchiveCommandTest.java
new file mode 100644
index 00000000000..c14f3278983
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteArchiveCommandTest.java
@@ -0,0 +1,106 @@
+package seedu.address.logic.commands;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.storage.AddressBookStorage;
+import seedu.address.storage.JsonAddressBookStorage;
+
+public class DeleteArchiveCommandTest {
+ private Path archiveDir;
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @BeforeEach
+ public void setUp() throws IOException {
+ archiveDir = Files.createDirectories(model.getArchiveDirectoryPath());
+ }
+
+ @AfterEach
+ public void tearDown() throws IOException {
+ if (!Files.exists(archiveDir)) {
+ return;
+ }
+
+ Files.walk(archiveDir)
+ .map(Path::toFile)
+ .forEach(file -> {
+ if (!file.delete()) {
+ file.deleteOnExit();
+ }
+ });
+ }
+
+ @Test
+ public void execute_archiveFileNotFound_failure() throws IOException {
+ Files.deleteIfExists(archiveDir);
+ Filename filename = new Filename("no_archive.json");
+ String expectedMessage = String.format(DeleteArchiveCommand.MESSAGE_NOT_FOUND, filename);
+ assertCommandFailure(new DeleteArchiveCommand(filename), model, expectedMessage);
+ }
+
+ @Test
+ public void execute_emptyArchiveFile_success() throws IOException {
+ // Create empty archive file
+ Filename filename = new Filename("empty_archive.json");
+ Path archiveFilepath = Paths.get(archiveDir.toString(), filename.toString());
+ Files.createFile(archiveFilepath);
+
+ String expectedMessage = String.format(DeleteArchiveCommand.MESSAGE_SUCCESS, filename);
+ assertCommandSuccess(new DeleteArchiveCommand(filename), model, expectedMessage, model);
+ }
+
+ @Test
+ public void execute_withArchiveFile_success() throws IOException {
+ // Save the current address book to a file
+ Path source = model.getAddressBookFilePath();
+ AddressBookStorage addressBookStorage = new JsonAddressBookStorage(source);
+ addressBookStorage.saveAddressBook(model.getAddressBook());
+
+ // Copy the address book file to the archive directory
+ Filename filename = new Filename("archive.json");
+ Path archiveFilepath = Paths.get(archiveDir.toString(), filename.toString());
+ Files.copy(source, archiveFilepath, REPLACE_EXISTING);
+
+ String expectedMessage = String.format(DeleteArchiveCommand.MESSAGE_SUCCESS, filename);
+ assertCommandSuccess(new DeleteArchiveCommand(filename), model, expectedMessage, model);
+ }
+
+ @Test
+ public void equals() {
+ DeleteArchiveCommand deleteArchiveFirstCommand = new DeleteArchiveCommand(new Filename("test1"));
+ DeleteArchiveCommand deleteArchiveSecondCommand = new DeleteArchiveCommand(new Filename("test2"));
+
+ // same object -> returns true
+ assertTrue(deleteArchiveFirstCommand.equals(deleteArchiveFirstCommand));
+
+ // same values -> returns true
+ DeleteArchiveCommand deleteArchiveFirstCommandCopy = new DeleteArchiveCommand(new Filename("test1"));
+ assertTrue(deleteArchiveFirstCommand.equals(deleteArchiveFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(deleteArchiveFirstCommand.equals("test1"));
+
+ // null -> returns false
+ assertFalse(deleteArchiveFirstCommand.equals(null));
+
+ // different command -> returns false
+ assertFalse(deleteArchiveFirstCommand.equals(deleteArchiveSecondCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandDescriptorTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandDescriptorTest.java
new file mode 100644
index 00000000000..a6ec1d59d8a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandDescriptorTest.java
@@ -0,0 +1,47 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.DeleteCommand.DeleteCommandDescriptor;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON;
+
+import org.junit.jupiter.api.Test;
+
+public class DeleteCommandDescriptorTest {
+
+ @Test
+ public void equals() {
+ // same values -> returns true
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ deleteCommandDescriptor.setEmergencyContactIndex(INDEX_FIRST_PERSON);
+
+ DeleteCommandDescriptor differentDeleteCommandDesciptor = new DeleteCommandDescriptor();
+ differentDeleteCommandDesciptor.setEmergencyContactIndex(INDEX_THIRD_PERSON);
+
+ DeleteCommandDescriptor descriptorCopy = new DeleteCommandDescriptor(deleteCommandDescriptor);
+ assertTrue(descriptorCopy.equals(deleteCommandDescriptor));
+
+ // same object -> returns true
+ assertTrue(deleteCommandDescriptor.equals(deleteCommandDescriptor));
+
+ // null -> returns false
+ assertFalse(deleteCommandDescriptor.equals(null));
+
+ // different types -> returns false
+ assertFalse(deleteCommandDescriptor.equals(5));
+
+ // different values -> returns false
+ assertFalse(deleteCommandDescriptor.equals(differentDeleteCommandDesciptor));
+
+ }
+
+ @Test
+ public void toStringMethod() {
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ String expected = DeleteCommandDescriptor.class.getCanonicalName() + "{emergency contact index="
+ + deleteCommandDescriptor.getEmergencyContactIndex().orElse(null) + "}";
+ assertEquals(expected, deleteCommandDescriptor.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..f18b3272a69 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -3,13 +3,22 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.logic.commands.DeleteCommand.DeleteCommandDescriptor;
+import static seedu.address.logic.commands.DeleteCommand.createEditedPerson;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.index.Index;
@@ -17,7 +26,11 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
/**
* Contains integration tests (interaction with the Model) and unit tests for
@@ -30,7 +43,7 @@ public class DeleteCommandTest {
@Test
public void execute_validIndexUnfilteredList_success() {
Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON, new DeleteCommandDescriptor());
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
@@ -44,7 +57,7 @@ public void execute_validIndexUnfilteredList_success() {
@Test
public void execute_invalidIndexUnfilteredList_throwsCommandException() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex, new DeleteCommandDescriptor());
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@@ -54,7 +67,7 @@ public void execute_validIndexFilteredList_success() {
showPersonAtIndex(model, INDEX_FIRST_PERSON);
Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON, new DeleteCommandDescriptor());
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
@@ -74,21 +87,87 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
// ensures that outOfBoundIndex is still in bounds of address book list
assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex, new DeleteCommandDescriptor());
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void createEditedPerson_success() {
+ Person personToEdit = model.getFilteredPersonList().get(0);
+ Set emergencyContactSet = new LinkedHashSet<>(Arrays.asList(new EmergencyContact(
+ new Name(VALID_ECNAME_AMY), new Phone(VALID_ECPHONE_AMY), new Relationship(VALID_ECRS_AMY))));
+ Person expectedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), emergencyContactSet, personToEdit.getDoctor(), personToEdit.getTags());
+ Person actualPerson = createEditedPerson(personToEdit, emergencyContactSet);
+
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void execute_deleteEmergencyContact_success() {
+ ModelManager actualModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EmergencyContact emergencyContactToDelete = new EmergencyContact(new Name("Birdie Wee"),
+ new Phone("94681732"), new Relationship("Sibling"));
+
+ Set updatedEmergencyContacts = new LinkedHashSet<>(personToDelete.getEmergencyContacts());
+ updatedEmergencyContacts.add(emergencyContactToDelete);
+ Person expectedPerson = new Person(personToDelete.getName(), personToDelete.getPhone(),
+ personToDelete.getEmail(), personToDelete.getAddress(), updatedEmergencyContacts,
+ personToDelete.getDoctor(), personToDelete.getTags());
+ actualModel.setPerson(personToDelete, expectedPerson);
+
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ deleteCommandDescriptor.setEmergencyContactIndex(Index.fromOneBased(2));
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON, deleteCommandDescriptor);
+
+ String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_EMERGENCY_CONTACT_SUCCESS,
+ Messages.formatEmergencyContact(emergencyContactToDelete));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+
+ assertCommandSuccess(deleteCommand, actualModel, expectedMessage, expectedModel);
+ }
+ @Test
+ public void execute_deleteEmergencyContact_failure() {
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+
+ assertTrue(outOfBoundIndex.getOneBased() > model.getAddressBook().getPersonList().get(0)
+ .getEmergencyContacts().size());
+
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ deleteCommandDescriptor.setEmergencyContactIndex(outOfBoundIndex);
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON,
+ deleteCommandDescriptor);
+
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_EMERGENCY_CONTACT_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_deleteLastEmergencyContact_failure() {
+ Person person = model.getAddressBook().getPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ assert person.getEmergencyContacts().size() < 2;
+
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ deleteCommandDescriptor.setEmergencyContactIndex(Index.fromOneBased(1));
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON,
+ deleteCommandDescriptor);
+
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_LAST_EMERGENCY_CONTACT_INDEX);
+ }
+
@Test
public void equals() {
- DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
- DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON);
+ DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON, new DeleteCommandDescriptor());
+ DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON, new DeleteCommandDescriptor());
// same object -> returns true
assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
// same values -> returns true
- DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON,
+ new DeleteCommandDescriptor());
assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
// different types -> returns false
@@ -104,7 +183,7 @@ public void equals() {
@Test
public void toStringMethod() {
Index targetIndex = Index.fromOneBased(1);
- DeleteCommand deleteCommand = new DeleteCommand(targetIndex);
+ DeleteCommand deleteCommand = new DeleteCommand(targetIndex, new DeleteCommandDescriptor());
String expected = DeleteCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
assertEquals(expected, deleteCommand.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..e6dad11b133 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -5,6 +5,12 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -24,6 +30,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -99,6 +106,32 @@ public void execute_filteredList_success() {
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
+ @Test
+ public void execute_editSecondEmergencyContact_success() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Model actualModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Person personInFilteredList = actualModel.getFilteredPersonList().get(0);
+ Person person = new PersonBuilder(personInFilteredList).addEmergencyContact(VALID_ECNAME_BOB, VALID_ECPHONE_BOB,
+ VALID_ECRS_BOB).build();
+ actualModel.setPerson(personInFilteredList, person);
+
+ Person editedPerson = new PersonBuilder(personInFilteredList).addEmergencyContact(VALID_ECNAME_AMY,
+ VALID_ECPHONE_AMY, VALID_ECRS_AMY).build();
+ Index index = Index.fromOneBased(2);
+ EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptorBuilder().withEmergencyContactIndex(index)
+ .withEmergencyContactName(VALID_ECNAME_AMY).withEmergencyContactPhone(VALID_ECPHONE_AMY)
+ .withEmergencyContactRelationship(VALID_ECRS_AMY).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, editPersonDescriptor);
+
+ String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ expectedModel.setPerson(expectedModel.getFilteredPersonList().get(0), editedPerson);
+
+ assertCommandSuccess(editCommand, actualModel, expectedMessage, expectedModel);
+ }
+
@Test
public void execute_duplicatePersonUnfilteredList_failure() {
Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
@@ -120,6 +153,24 @@ public void execute_duplicatePersonFilteredList_failure() {
assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
}
+ @Test
+ public void execute_duplicateEmergencyContactFilteredList_failure() {
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ // edit person in filtered list into a duplicate in address book
+ Person personInList = expectedModel.getAddressBook().getPersonList().get(6);
+ assert personInList.getEmergencyContacts().size() > 1;
+ EmergencyContact emergencyContact = personInList.getFirstEmergencyContact();
+ EditPersonDescriptorBuilder editPersonDescriptorBuilder = new EditPersonDescriptorBuilder()
+ .withEmergencyContactIndex(INDEX_SECOND_PERSON)
+ .withEmergencyContactName(emergencyContact.getName().fullName)
+ .withEmergencyContactPhone(emergencyContact.getPhone().value)
+ .withEmergencyContactRelationship(emergencyContact.getRelationship().relationship);
+ EditCommand editCommand = new EditCommand(Index.fromOneBased(7),
+ editPersonDescriptorBuilder.build());
+
+ assertCommandFailure(editCommand, model, AddEmergencyContactCommand.MESSAGE_DUPLICATE_EMERGENCY_CONTACT);
+ }
+
@Test
public void execute_invalidPersonIndexUnfilteredList_failure() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
@@ -146,6 +197,19 @@ public void execute_invalidPersonIndexFilteredList_failure() {
assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_invalidEmergencyContactIndexFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ assertTrue(outOfBoundIndex.getOneBased() > model.getAddressBook().getPersonList().get(0)
+ .getEmergencyContacts().size());
+
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptorBuilder()
+ .withName(VALID_NAME_BOB).withEmergencyContactIndex(outOfBoundIndex).build());
+
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_EMERGENCY_CONTACT_DISPLAYED_INDEX);
+ }
+
@Test
public void equals() {
final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..1114ab93548 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -64,7 +64,15 @@ public void toStringMethod() {
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
+ + editPersonDescriptor.getAddress().orElse(null) + ", index of emergency contact to edit="
+ + editPersonDescriptor.getIndexOfEmergencyContactToEdit().orElse(null) + ", emergency contact name="
+ + editPersonDescriptor.getEmergencyContactName().orElse(null) + ", emergency contact phone="
+ + editPersonDescriptor.getEmergencyContactPhone().orElse(null)
+ + ", emergency contact relationship="
+ + editPersonDescriptor.getEmergencyContactRelationship().orElse(null) + ", doctor name="
+ + editPersonDescriptor.getDoctorName().orElse(null) + ", doctor phone="
+ + editPersonDescriptor.getDoctorPhone().orElse(null) + ", doctor email="
+ + editPersonDescriptor.getDoctorEmail().orElse(null) + ", tags="
+ editPersonDescriptor.getTags().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/FindDoctorCommandTest.java b/src/test/java/seedu/address/logic/commands/FindDoctorCommandTest.java
new file mode 100644
index 00000000000..2ec340d1301
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FindDoctorCommandTest.java
@@ -0,0 +1,92 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.CARL;
+import static seedu.address.testutil.TypicalPersons.ELLE;
+import static seedu.address.testutil.TypicalPersons.FIONA;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.DoctorNameContainsKeywordsPredicate;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code FindDoctorCommand}.
+ */
+public class FindDoctorCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ DoctorNameContainsKeywordsPredicate firstPredicate =
+ new DoctorNameContainsKeywordsPredicate(Collections.singletonList("first"));
+ DoctorNameContainsKeywordsPredicate secondPredicate =
+ new DoctorNameContainsKeywordsPredicate(Collections.singletonList("second"));
+
+ FindDoctorCommand findFirstCommand = new FindDoctorCommand(firstPredicate);
+ FindDoctorCommand findSecondCommand = new FindDoctorCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ FindDoctorCommand findFirstCommandCopy = new FindDoctorCommand(firstPredicate);
+ assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(findFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(findFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(findFirstCommand.equals(findSecondCommand));
+ }
+
+ @Test
+ public void execute_zeroKeywords_noPersonFound() {
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ DoctorNameContainsKeywordsPredicate predicate = preparePredicate(" ");
+ FindDoctorCommand command = new FindDoctorCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_multipleKeywords_multiplePersonsFound() {
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
+ DoctorNameContainsKeywordsPredicate predicate = preparePredicate("Po Wong Bing");
+ FindDoctorCommand command = new FindDoctorCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void toStringMethod() {
+ DoctorNameContainsKeywordsPredicate predicate = new DoctorNameContainsKeywordsPredicate(
+ Arrays.asList("keyword"));
+ FindDoctorCommand findDoctorCommand = new FindDoctorCommand(predicate);
+ String expected = FindDoctorCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
+ assertEquals(expected, findDoctorCommand.toString());
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code DoctorNameContainsKeywordsPredicate}.
+ */
+ private DoctorNameContainsKeywordsPredicate preparePredicate(String userInput) {
+ return new DoctorNameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListArchiveFilesCommandTest.java b/src/test/java/seedu/address/logic/commands/ListArchiveFilesCommandTest.java
new file mode 100644
index 00000000000..5533661a0f5
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListArchiveFilesCommandTest.java
@@ -0,0 +1,70 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+
+public class ListArchiveFilesCommandTest {
+ private final Model model = new ModelManager();
+ private Path archiveDir;
+
+ @BeforeEach
+ public void setUp() throws IOException {
+ archiveDir = Files.createDirectories(model.getArchiveDirectoryPath());
+ }
+
+ @AfterEach
+ public void tearDown() throws IOException {
+ if (!Files.exists(archiveDir)) {
+ return;
+ }
+
+ Files.walk(archiveDir)
+ .map(Path::toFile)
+ .forEach(file -> {
+ if (!file.delete()) {
+ file.deleteOnExit();
+ }
+ });
+ }
+
+ @Test
+ public void execute_noArchiveDirectory_failure() throws IOException {
+ Files.deleteIfExists(archiveDir);
+ assertCommandFailure(new ListArchiveFilesCommand(), model, ListArchiveFilesCommand.MESSAGE_NO_ARCHIVE);
+ }
+
+ @Test
+ public void execute_noArchiveFiles_failure() {
+ assertCommandFailure(new ListArchiveFilesCommand(), model, ListArchiveFilesCommand.MESSAGE_NO_ARCHIVE);
+ }
+
+ @Test
+ public void execute_withArchiveFiles_success() throws IOException {
+ Files.createFile(Path.of(archiveDir.toString(), "archive.json"));
+ String expectedMessage = ListArchiveFilesCommand.MESSAGE_SUCCESS + "\n" + "archive.json";
+ assertCommandSuccess(new ListArchiveFilesCommand(), model, expectedMessage, model);
+ }
+
+ @Test
+ @EnabledOnOs({OS.WINDOWS, OS.LINUX})
+ public void execute_withMultipleArchiveFiles_success() throws IOException {
+ Files.createFile(Path.of(archiveDir.toString(), "multiple_archive1.json"));
+ Files.createFile(Path.of(archiveDir.toString(), "multiple_archive2.json"));
+ String expectedMessage = ListArchiveFilesCommand.MESSAGE_SUCCESS + "\n" + "multiple_archive1.json\n"
+ + "multiple_archive2.json";
+ assertCommandSuccess(new ListArchiveFilesCommand(), model, expectedMessage, model);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/LoadArchiveCommandTest.java b/src/test/java/seedu/address/logic/commands/LoadArchiveCommandTest.java
new file mode 100644
index 00000000000..3d3212efd97
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/LoadArchiveCommandTest.java
@@ -0,0 +1,106 @@
+package seedu.address.logic.commands;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.storage.AddressBookStorage;
+import seedu.address.storage.JsonAddressBookStorage;
+
+public class LoadArchiveCommandTest {
+ private Path archiveDir;
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @BeforeEach
+ public void setUp() throws IOException {
+ archiveDir = Files.createDirectories(model.getArchiveDirectoryPath());
+ }
+
+ @AfterEach
+ public void tearDown() throws IOException {
+ if (!Files.exists(archiveDir)) {
+ return;
+ }
+
+ Files.walk(archiveDir)
+ .map(Path::toFile)
+ .forEach(file -> {
+ if (!file.delete()) {
+ file.deleteOnExit();
+ }
+ });
+ }
+
+ @Test
+ public void execute_archiveFileNotFound_failure() throws IOException {
+ Files.deleteIfExists(archiveDir);
+ Filename filename = new Filename("no_archive.json");
+ String expectedMessage = String.format(LoadArchiveCommand.MESSAGE_NOT_FOUND, filename);
+ assertCommandFailure(new LoadArchiveCommand(filename), model, expectedMessage);
+ }
+
+ @Test
+ public void execute_errorLoadingArchiveFile_failure() throws IOException {
+ // Create empty archive file
+ Filename filename = new Filename("error_archive.json");
+ Path archiveFilepath = Paths.get(archiveDir.toString(), filename.toString());
+ Files.createFile(archiveFilepath);
+
+ String expectedMessage = String.format(LoadArchiveCommand.MESSAGE_FAILURE, filename);
+ assertCommandFailure(new LoadArchiveCommand(filename), model, expectedMessage);
+ }
+
+ @Test
+ public void execute_withArchiveFile_success() throws IOException {
+ // Save the current address book to a file
+ Path source = model.getAddressBookFilePath();
+ AddressBookStorage addressBookStorage = new JsonAddressBookStorage(source);
+ addressBookStorage.saveAddressBook(model.getAddressBook());
+
+ // Copy the address book file to the archive directory
+ Filename filename = new Filename("archive.json");
+ Path archiveFilepath = Paths.get(archiveDir.toString(), filename.toString());
+ Files.copy(source, archiveFilepath, REPLACE_EXISTING);
+
+ String expectedMessage = String.format(LoadArchiveCommand.MESSAGE_SUCCESS, filename);
+ assertCommandSuccess(new LoadArchiveCommand(filename), model, expectedMessage, model);
+ }
+
+ @Test
+ public void equals() {
+ LoadArchiveCommand loadArchiveFirstCommand = new LoadArchiveCommand(new Filename("test1"));
+ LoadArchiveCommand loadArchiveSecondCommand = new LoadArchiveCommand(new Filename("test2"));
+
+ // same object -> returns true
+ assertTrue(loadArchiveFirstCommand.equals(loadArchiveFirstCommand));
+
+ // same values -> returns true
+ LoadArchiveCommand loadArchiveFirstCommandCopy = new LoadArchiveCommand(new Filename("test1"));
+ assertTrue(loadArchiveFirstCommand.equals(loadArchiveFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(loadArchiveFirstCommand.equals("test1"));
+
+ // null -> returns false
+ assertFalse(loadArchiveFirstCommand.equals(null));
+
+ // different command -> returns false
+ assertFalse(loadArchiveFirstCommand.equals(loadArchiveSecondCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
new file mode 100644
index 00000000000..d57211ea9be
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
@@ -0,0 +1,94 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+import seedu.address.testutil.TypicalPersons;
+
+class RedoCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(TypicalPersons.getTypicalAddressBook(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_noRedoAvailable_throwsCommandException() {
+ model = new ModelManager(new AddressBook(), new UserPrefs());
+ RedoCommand redoCommand = new RedoCommand();
+
+ // Verify that the exception is thrown when there are no redoable states
+ CommandException exception = assertThrows(CommandException.class, () -> redoCommand.execute(model));
+ assertEquals(RedoCommand.MESSAGE_FAILURE, exception.getMessage());
+ }
+
+ @Test
+ public void execute_redoAvailable_success() throws Exception {
+ Person uniquePerson = new PersonBuilder().withName("Unique Person").build();
+
+ model.addPerson(uniquePerson);
+ model.saveAddressBook();
+
+ expectedModel.addPerson(uniquePerson);
+ expectedModel.saveAddressBook();
+
+ model.undoAddressBook();
+ expectedModel.undoAddressBook();
+
+ CommandResult result = new RedoCommand().execute(model);
+ expectedModel.redoAddressBook();
+
+ assertEquals(RedoCommand.MESSAGE_SUCCESS, result.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+ }
+
+ @Test
+ public void execute_multipleRedo_success() throws Exception {
+ Person person1 = new PersonBuilder().withPhone(VALID_PHONE_AMY).build();
+ Person person2 = new PersonBuilder().withPhone(VALID_PHONE_BOB).build();
+
+ model.addPerson(person1);
+ model.addPerson(person2);
+ model.saveAddressBook();
+
+ expectedModel.addPerson(person1);
+ expectedModel.addPerson(person2);
+ expectedModel.saveAddressBook();
+
+ model.undoAddressBook();
+ expectedModel.undoAddressBook();
+ model.undoAddressBook();
+ expectedModel.undoAddressBook();
+
+ assertTrue(model.canRedoAddressBook());
+
+ CommandResult result1 = new RedoCommand().execute(model);
+ expectedModel.redoAddressBook();
+ assertEquals(RedoCommand.MESSAGE_SUCCESS, result1.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+
+ CommandResult result2 = new RedoCommand().execute(model);
+ expectedModel.redoAddressBook();
+ assertEquals(RedoCommand.MESSAGE_SUCCESS, result2.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
new file mode 100644
index 00000000000..e0c98eb3596
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
@@ -0,0 +1,86 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+import seedu.address.testutil.TypicalPersons;
+
+class UndoCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(TypicalPersons.getTypicalAddressBook(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_noUndoAvailable_throwsCommandException() {
+ model = new ModelManager(new AddressBook(), new UserPrefs());
+ UndoCommand undoCommand = new UndoCommand();
+
+ // Verify that the exception is thrown when there are no undoable states
+ CommandException exception = assertThrows(CommandException.class, () -> undoCommand.execute(model));
+ assertEquals(UndoCommand.MESSAGE_FAILURE, exception.getMessage());
+ }
+
+ @Test
+ public void execute_undoAvailable_success() throws Exception {
+ Person uniquePerson = new PersonBuilder().withName("Unique Person").build();
+
+ model.addPerson(uniquePerson);
+ model.saveAddressBook();
+
+ expectedModel.addPerson(uniquePerson);
+ expectedModel.saveAddressBook();
+
+ CommandResult result = new UndoCommand().execute(model);
+ expectedModel.undoAddressBook();
+
+ assertEquals(UndoCommand.MESSAGE_SUCCESS, result.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+ }
+
+ @Test
+ public void execute_multipleUndo_success() throws Exception {
+ Person person1 = new PersonBuilder().withPhone(VALID_PHONE_AMY).build();
+ Person person2 = new PersonBuilder().withPhone(VALID_PHONE_BOB).build();
+
+ model.addPerson(person1);
+ model.addPerson(person2);
+ model.saveAddressBook();
+
+ expectedModel.addPerson(person1);
+ expectedModel.addPerson(person2);
+ expectedModel.saveAddressBook();
+
+ assertTrue(model.canUndoAddressBook());
+
+ CommandResult result1 = new UndoCommand().execute(model);
+ expectedModel.undoAddressBook();
+ assertEquals(UndoCommand.MESSAGE_SUCCESS, result1.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+
+ CommandResult result2 = new UndoCommand().execute(model);
+ expectedModel.undoAddressBook();
+ assertEquals(UndoCommand.MESSAGE_SUCCESS, result2.getFeedbackToUser());
+ assertEquals(expectedModel, model);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..5246ebb2afd 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -3,9 +3,24 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_EMAIL_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_NAME_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DOC_PHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DOC_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DOC_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DOC_PHONE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
@@ -25,7 +40,13 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
@@ -38,6 +59,7 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
import seedu.address.model.person.Address;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
@@ -54,21 +76,27 @@ public void parse_allFieldsPresent_success() {
// whitespace only preamble
assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ + ADDRESS_DESC_BOB + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
// multiple tags - all accepted
Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
new AddCommand(expectedPersonMultipleTags));
}
@Test
public void parse_repeatedNonTagValue_failure() {
String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ + ADDRESS_DESC_BOB + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB + TAG_DESC_FRIEND;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -90,7 +118,9 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser,
validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
+ validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE,
+ PREFIX_EMERGENCY_CONTACT_NAME, PREFIX_EMERGENCY_CONTACT_PHONE,
+ PREFIX_EMERGENCY_CONTACT_RELATIONSHIP, PREFIX_DOC_NAME, PREFIX_DOC_PHONE, PREFIX_DOC_EMAIL));
// invalid value followed by valid value
@@ -133,7 +163,9 @@ public void parse_repeatedNonTagValue_failure() {
public void parse_optionalFieldsMissing_success() {
// zero tags
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY
+ + ADDRESS_DESC_AMY + ECNAME_DESC_AMY + ECPHONE_DESC_AMY + ECRS_DESC_AMY
+ + DOC_NAME_DESC_AMY + DOC_PHONE_DESC_AMY + DOC_EMAIL_DESC_AMY,
new AddCommand(expectedPerson));
}
@@ -166,31 +198,62 @@ public void parse_compulsoryFieldMissing_failure() {
public void parse_invalidValue_failure() {
// invalid name
assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
// invalid phone
assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
// invalid email
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
// invalid address
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ // invalid doctor name
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + INVALID_DOC_NAME_DESC + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, DoctorName.MESSAGE_CONSTRAINTS);
+
+ // invalid doctor phone
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + INVALID_DOC_PHONE_DESC + DOC_EMAIL_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+
+ // invalid doctor email
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + INVALID_DOC_EMAIL_DESC
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+
// invalid tag
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ECNAME_DESC_BOB
+ + ECPHONE_DESC_BOB + ECRS_DESC_BOB + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ + INVALID_ADDRESS_DESC, Name.MESSAGE_CONSTRAINTS);
// non-empty preamble
assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ + ADDRESS_DESC_BOB + ECNAME_DESC_BOB + ECPHONE_DESC_BOB + ECRS_DESC_BOB
+ + DOC_NAME_DESC_BOB + DOC_PHONE_DESC_BOB + DOC_EMAIL_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddEmergencyContactCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddEmergencyContactCommandParserTest.java
new file mode 100644
index 00000000000..00d7b177609
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddEmergencyContactCommandParserTest.java
@@ -0,0 +1,106 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ECNAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ECPHONE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_BOB;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalPersons.BOB;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.AddEmergencyContactCommand;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
+import seedu.address.testutil.PersonBuilder;
+
+public class AddEmergencyContactCommandParserTest {
+
+ private static final String VALID_DESC = ECNAME_DESC_AMY + ECPHONE_DESC_AMY + ECRS_DESC_AMY;
+
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEmergencyContactCommand.MESSAGE_USAGE);
+
+ private AddEmergencyContactCommandParser parser = new AddEmergencyContactCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, VALID_ECNAME_AMY, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, "a "
+ + VALID_DESC, MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5" + VALID_DESC, MESSAGE_INVALID_FORMAT);
+
+ // zero index
+ assertParseFailure(parser, "0" + VALID_DESC, MESSAGE_INVALID_FORMAT);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ assertParseFailure(parser, "1" + INVALID_ECNAME_DESC
+ + ECPHONE_DESC_AMY + ECRS_DESC_AMY, Name.MESSAGE_CONSTRAINTS); // invalid name
+ // invalid phone
+ assertParseFailure(parser, "1" + ECNAME_DESC_AMY + INVALID_ECPHONE_DESC + ECRS_DESC_AMY,
+ Phone.MESSAGE_CONSTRAINTS);
+ // invalid relationship
+ assertParseFailure(parser, "1" + ECNAME_DESC_AMY + ECPHONE_DESC_AMY
+ + " " + PREFIX_EMERGENCY_CONTACT_RELATIONSHIP + "Alien",
+ Relationship.RELATIONSHIP_TYPE_CONSTRAINTS);
+
+ // multiple invalid values, but only the first invalid value is captured
+ assertParseFailure(parser, "1" + INVALID_ECNAME_DESC + INVALID_ECPHONE_DESC + ECRS_DESC_AMY,
+ Name.MESSAGE_CONSTRAINTS);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ Person samplePerson = new PersonBuilder(BOB).build();
+ Set emergencyContactSet =
+ new LinkedHashSet<>(samplePerson.getEmergencyContacts());
+ EmergencyContact emergencyContactToAdd = new EmergencyContact(new Name(VALID_ECNAME_BOB),
+ new Phone(VALID_ECPHONE_BOB), new Relationship(VALID_ECRS_BOB));
+ emergencyContactSet.add(emergencyContactToAdd);
+ Person expectedPerson = new Person(samplePerson.getName(), samplePerson.getPhone(), samplePerson.getEmail(),
+ samplePerson.getAddress(), emergencyContactSet, samplePerson.getDoctor(), samplePerson.getTags());
+
+ // whitespace only preamble
+ assertParseSuccess(parser, "1 " + ECNAME_DESC_BOB + ECPHONE_DESC_BOB
+ + ECRS_DESC_BOB, new AddEmergencyContactCommand(INDEX_FIRST_PERSON, emergencyContactToAdd));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..a3ddcf7ef81 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -4,6 +4,12 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -14,15 +20,25 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddEmergencyContactCommand;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.DeleteArchiveCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteCommand.DeleteCommandDescriptor;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindDoctorCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ListArchiveFilesCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadArchiveCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.DoctorNameContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -50,7 +66,7 @@ public void parseCommand_clear() throws Exception {
public void parseCommand_delete() throws Exception {
DeleteCommand command = (DeleteCommand) parser.parseCommand(
DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
- assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command);
+ assertEquals(new DeleteCommand(INDEX_FIRST_PERSON, new DeleteCommandDescriptor()), command);
}
@Test
@@ -62,6 +78,17 @@ public void parseCommand_edit() throws Exception {
assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
}
+ @Test
+ public void parseCommand_addEmergencyContact() throws Exception {
+ Person person = new PersonBuilder()
+ .withEmergencyContact(VALID_ECNAME_AMY, VALID_ECPHONE_AMY, VALID_ECRS_AMY).build();
+ String descriptor = ECNAME_DESC_AMY + ECPHONE_DESC_AMY + ECRS_DESC_AMY;
+ AddEmergencyContactCommand command = (AddEmergencyContactCommand) parser.parseCommand(
+ AddEmergencyContactCommand.COMMAND_WORD + " "
+ + INDEX_FIRST_PERSON.getOneBased() + descriptor);
+ assertEquals(new AddEmergencyContactCommand(INDEX_FIRST_PERSON, person.getFirstEmergencyContact()), command);
+ }
+
@Test
public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand);
@@ -85,13 +112,56 @@ public void parseCommand_help() throws Exception {
@Test
public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand);
- assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
+ assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " name desc") instanceof ListCommand);
+ }
+
+ @Test
+ public void parseCommand_archive() throws Exception {
+ assertTrue(parser.parseCommand(ArchiveCommand.COMMAND_WORD) instanceof ArchiveCommand);
+ assertTrue(parser.parseCommand(ArchiveCommand.COMMAND_WORD + " test") instanceof ArchiveCommand);
+ }
+
+ @Test
+ public void parseCommand_listArchiveFiles() throws Exception {
+ assertTrue(parser.parseCommand(ListArchiveFilesCommand.COMMAND_WORD) instanceof ListArchiveFilesCommand);
+ assertTrue(parser.parseCommand(ListArchiveFilesCommand.COMMAND_WORD + " 3")
+ instanceof ListArchiveFilesCommand);
+ }
+
+ @Test
+ public void parseCommand_loadArchive() throws Exception {
+ assertTrue(parser.parseCommand(LoadArchiveCommand.COMMAND_WORD + " test.json")
+ instanceof LoadArchiveCommand);
+ }
+
+ @Test
+ public void parseCommand_deleteArchive() throws Exception {
+ assertTrue(parser.parseCommand(DeleteArchiveCommand.COMMAND_WORD + " test.json")
+ instanceof DeleteArchiveCommand);
+ }
+
+ @Test
+ public void parseCommand_undo() throws Exception {
+ assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD) instanceof UndoCommand);
+ }
+
+ @Test
+ public void parseCommand_redo() throws Exception {
+ assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD) instanceof RedoCommand);
+ }
+
+ @Test
+ public void parseCommand_find_doctor() throws Exception {
+ List keywords = Arrays.asList("foo", "bar", "baz");
+ FindDoctorCommand command = (FindDoctorCommand) parser.parseCommand(FindDoctorCommand.COMMAND_WORD
+ + " " + keywords.stream().collect(Collectors.joining(" ")));
+ assertEquals(new FindDoctorCommand(new DoctorNameContainsKeywordsPredicate(keywords)), command);
}
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
- -> parser.parseCommand(""));
+ -> parser.parseCommand(""));
}
@Test
diff --git a/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java
new file mode 100644
index 00000000000..b9400044ffb
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.ArchiveCommand;
+
+public class ArchiveCommandParserTest {
+
+ private final ArchiveCommandParser parser = new ArchiveCommandParser();
+
+ @Test
+ public void parse_validArgs_returnArchiveCommand() {
+ assertParseSuccess(parser, "Test", new ArchiveCommand(new Filename("Test")));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "/", String.format(Filename.MESSAGE_CONSTRAINTS));
+ }
+
+ @Test
+ public void parse_blankArgs_throwsParseException() {
+ assertParseSuccess(parser, " ", new ArchiveCommand());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteArchiveCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteArchiveCommandParserTest.java
new file mode 100644
index 00000000000..95cc0dc892f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteArchiveCommandParserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.DeleteArchiveCommand;
+
+public class DeleteArchiveCommandParserTest {
+
+ private final DeleteArchiveCommandParser parser = new DeleteArchiveCommandParser();
+
+ @Test
+ public void parse_validArgs_returnDeleteArchiveCommand() {
+ assertParseSuccess(parser, "Test", new DeleteArchiveCommand(new Filename("Test")));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "/", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Filename.MESSAGE_CONSTRAINTS + "\n\n" + DeleteArchiveCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_blankArgs_throwsParseException() {
+ assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Filename.MESSAGE_CONSTRAINTS_BLANK + "\n\n" + DeleteArchiveCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
index 6a40e14a649..83b4878a0e1 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
@@ -1,6 +1,7 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -8,6 +9,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteCommand.DeleteCommandDescriptor;
/**
* As we are only doing white-box testing, our test cases do not cover path variations
@@ -22,11 +24,29 @@ public class DeleteCommandParserTest {
@Test
public void parse_validArgs_returnsDeleteCommand() {
- assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON));
+ assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON,
+ new DeleteCommandDescriptor()));
}
@Test
public void parse_invalidArgs_throwsParseException() {
- assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ DeleteCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_validEmergencyContactIndex_returnsDeleteCommand() {
+ DeleteCommandDescriptor deleteCommandDescriptor = new DeleteCommandDescriptor();
+ deleteCommandDescriptor.setEmergencyContactIndex(INDEX_FIRST_PERSON);
+ assertParseSuccess(parser, "1 " + PREFIX_EMERGENCY_CONTACT_TO_EDIT
+ + String.valueOf(INDEX_FIRST_PERSON.getOneBased()),
+ new DeleteCommand(INDEX_FIRST_PERSON, deleteCommandDescriptor));
+ }
+
+ @Test
+ public void parse_invalidEmergencyContactIndex_failure() {
+ assertParseFailure(parser, "1 " + PREFIX_EMERGENCY_CONTACT_TO_EDIT + "abc",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ DeleteCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..dc4faa3cc41 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -3,6 +3,9 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ECNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECPHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ECRS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
@@ -24,6 +27,7 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
@@ -205,4 +209,23 @@ public void parse_resetTags_success() {
assertParseSuccess(parser, userInput, expectedCommand);
}
+
+ @Test
+ public void parse_isEmergencyIndexProvided_failure() {
+ Index targetIndex = INDEX_FIRST_PERSON;
+ String targetIndexString = String.valueOf(targetIndex.getOneBased());
+ String userInput = targetIndexString + ECNAME_DESC_AMY + ECPHONE_DESC_AMY + ECRS_DESC_AMY;
+
+ assertParseFailure(parser, userInput, EditCommand.MESSAGE_EMERGENCY_CONTACT_FIELDS_INVALID);
+ }
+
+ @Test
+ public void parse_isEmergencyContactFieldsProvided_failure() {
+ Index targetIndex = INDEX_FIRST_PERSON;
+ String targetIndexString = String.valueOf(targetIndex.getOneBased());
+ String userInput = targetIndexString + " " + PREFIX_EMERGENCY_CONTACT_TO_EDIT
+ + targetIndexString;
+
+ assertParseFailure(parser, userInput, EditCommand.MESSAGE_EMERGENCY_CONTACT_NOT_EDITED);
+ }
}
diff --git a/src/test/java/seedu/address/logic/parser/FindDoctorCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindDoctorCommandParserTest.java
new file mode 100644
index 00000000000..d74763092ea
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FindDoctorCommandParserTest.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FindDoctorCommand;
+import seedu.address.model.person.DoctorNameContainsKeywordsPredicate;
+
+public class FindDoctorCommandParserTest {
+
+ private FindDoctorCommandParser parser = new FindDoctorCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindDoctorCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_validArgs_returnsFindDoctorCommand() {
+ // no leading and trailing whitespaces
+ FindDoctorCommand expectedFindDoctorCommand =
+ new FindDoctorCommand(new DoctorNameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")));
+ assertParseSuccess(parser, "Alice Bob", expectedFindDoctorCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindDoctorCommand);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/LoadArchiveCommandParserTest.java b/src/test/java/seedu/address/logic/parser/LoadArchiveCommandParserTest.java
new file mode 100644
index 00000000000..7049b6adb3a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/LoadArchiveCommandParserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.filename.Filename;
+import seedu.address.logic.commands.LoadArchiveCommand;
+
+public class LoadArchiveCommandParserTest {
+
+ private final LoadArchiveCommandParser parser = new LoadArchiveCommandParser();
+
+ @Test
+ public void parse_validArgs_returnLoadArchiveCommand() {
+ assertParseSuccess(parser, "Test", new LoadArchiveCommand(new Filename("Test")));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "/", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Filename.MESSAGE_CONSTRAINTS + "\n\n" + LoadArchiveCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_blankArgs_throwsParseException() {
+ assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Filename.MESSAGE_CONSTRAINTS_BLANK + "\n\n" + LoadArchiveCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..39126d75fbf 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -13,26 +13,33 @@
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.filename.Filename;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
public class ParserUtilTest {
- private static final String INVALID_NAME = "R@chel";
+ private static final String INVALID_FILENAME = "/";
+ private static final String INVALID_NAME = "R#chel";
private static final String INVALID_PHONE = "+651234";
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_RELATIONSHIP = "@bs!~Ho)";
+ private static final String INVALID_RELATIONSHIP_TYPE = "Teacher";
+ private static final String VALID_FILENAME = "1st Quarter 2021";
private static final String VALID_NAME = "Rachel Walker";
private static final String VALID_PHONE = "123456";
private static final String VALID_ADDRESS = "123 Main Street #0505";
private static final String VALID_EMAIL = "rachel@example.com";
private static final String VALID_TAG_1 = "friend";
private static final String VALID_TAG_2 = "neighbour";
+ private static final String VALID_RELATIONSHIP = "Son";
private static final String WHITESPACE = " \t\r\n";
@@ -44,7 +51,7 @@ public void parseIndex_invalidInput_throwsParseException() {
@Test
public void parseIndex_outOfRangeInput_throwsParseException() {
assertThrows(ParseException.class, MESSAGE_INVALID_INDEX, ()
- -> ParserUtil.parseIndex(Long.toString(Integer.MAX_VALUE + 1)));
+ -> ParserUtil.parseIndex(Long.toString(Integer.MAX_VALUE + 1)));
}
@Test
@@ -56,9 +63,32 @@ public void parseIndex_validInput_success() throws Exception {
assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 "));
}
+ @Test
+ public void parseFilename_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseFilename(null));
+ }
+
+ @Test
+ public void parseFilename_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseFilename(INVALID_FILENAME));
+ }
+
+ @Test
+ public void parseFilename_validValueWithoutWhitespace_returnsFilename() throws Exception {
+ Filename expectedName = new Filename(VALID_FILENAME);
+ assertEquals(expectedName, ParserUtil.parseFilename(VALID_FILENAME));
+ }
+
+ @Test
+ public void parseFilename_validValueWithWhitespace_returnsTrimmedFilename() throws Exception {
+ String filenameWithWhitespace = WHITESPACE + VALID_FILENAME + WHITESPACE;
+ Filename expectedName = new Filename(VALID_FILENAME);
+ assertEquals(expectedName, ParserUtil.parseFilename(filenameWithWhitespace));
+ }
+
@Test
public void parseName_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseName((String) null));
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseName(null));
}
@Test
@@ -81,7 +111,7 @@ public void parseName_validValueWithWhitespace_returnsTrimmedName() throws Excep
@Test
public void parsePhone_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parsePhone((String) null));
+ assertThrows(NullPointerException.class, () -> ParserUtil.parsePhone(null));
}
@Test
@@ -104,7 +134,7 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc
@Test
public void parseAddress_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress(null));
}
@Test
@@ -127,7 +157,7 @@ public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws
@Test
public void parseEmail_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null));
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail(null));
}
@Test
@@ -148,6 +178,34 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc
assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace));
}
+ @Test
+ public void parseRelationship_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseRelationship(null));
+ }
+
+ @Test
+ public void parseRelationship_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseRelationship(INVALID_RELATIONSHIP));
+ }
+
+ @Test
+ public void parseRelationship_invalidRelationshipType_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseRelationship(INVALID_RELATIONSHIP_TYPE));
+ }
+
+ @Test
+ public void parseRelationship_validValueWithoutWhitespace_returnsRelationship() throws Exception {
+ Relationship expectedRelationship = new Relationship(VALID_RELATIONSHIP);
+ assertEquals(expectedRelationship, ParserUtil.parseRelationship(VALID_RELATIONSHIP));
+ }
+
+ @Test
+ public void parseRelationship_validValueWithWhitespace_returnsTrimmedRelationship() throws Exception {
+ String relationshipWithWhitespace = WHITESPACE + VALID_RELATIONSHIP + WHITESPACE;
+ Relationship expectedRelationship = new Relationship(VALID_RELATIONSHIP);
+ assertEquals(expectedRelationship, ParserUtil.parseRelationship(relationshipWithWhitespace));
+ }
+
@Test
public void parseTag_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null));
@@ -189,7 +247,7 @@ public void parseTags_emptyCollection_returnsEmptySet() throws Exception {
@Test
public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2));
- Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2)));
+ Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2)));
assertEquals(expectedTagSet, actualTagSet);
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..bc6e7346a0f 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -88,6 +88,94 @@ public void hasPerson_personInAddressBook_returnsTrue() {
assertTrue(modelManager.hasPerson(ALICE));
}
+ // ============ Undo and Redo Methods ================================================================
+ @Test
+ public void undoAddressBook_personSuccessfullyRemovedAfterUndo() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ assertTrue(modelManager.hasPerson(BENSON), "BENSON should be present in the address book after adding.");
+
+ modelManager.undoAddressBook();
+
+ assertFalse(modelManager.hasPerson(BENSON), "BENSON should not be in the address book after undo.");
+ }
+
+ @Test
+ public void canUndoAddressBook_noSavedState_undoNotPossible() {
+ assertFalse(modelManager.canUndoAddressBook(), "Undo should not be possible when no state has been saved.");
+ }
+
+ @Test
+ public void undoAddressBook_noUndoableState_undoFails() {
+ assertFalse(modelManager.canUndoAddressBook(), "Undo should fail when no undoable state exists.");
+ }
+
+ @Test
+ public void canUndoAddressBook_savedStateExists_undoPossible() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ assertTrue(modelManager.canUndoAddressBook(), "Undo should be possible after saving a state.");
+ }
+
+ @Test
+ public void redoAddressBook_personSuccessfullyRestoredAfterRedo() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ modelManager.undoAddressBook();
+
+ assertFalse(modelManager.hasPerson(BENSON), "BENSON should not be in the address book after undo.");
+
+ modelManager.redoAddressBook();
+
+ assertTrue(modelManager.hasPerson(BENSON), "BENSON should be back in the address book after redo.");
+ }
+
+ @Test
+ public void canRedoAddressBook_noSavedState_redoNotPossible() {
+ assertFalse(modelManager.canRedoAddressBook(), "Redo should not be possible when no state has been saved.");
+ }
+
+ @Test
+ public void redoAddressBook_noRedoableState_redoFails() {
+ assertFalse(modelManager.canRedoAddressBook(), "Redo should not be possible without first performing an undo.");
+ }
+
+ @Test
+ public void canRedoAddressBook_savedStateExists_redoPossible() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ modelManager.undoAddressBook();
+
+ assertTrue(modelManager.canRedoAddressBook(), "Redo should be possible after performing an undo.");
+ }
+
+ @Test
+ public void saveAddressBook_newStateAfterUndo_redoNotPossible() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ modelManager.undoAddressBook();
+
+ modelManager.addPerson(ALICE);
+ modelManager.saveAddressBook();
+
+ assertFalse(modelManager.canRedoAddressBook(), "Redo should not be possible after making new changes.");
+ }
+
+ @Test
+ public void saveAddressBook_stateSaved_undoPossible() {
+ modelManager.addPerson(BENSON);
+ modelManager.saveAddressBook();
+
+ assertTrue(modelManager.canUndoAddressBook(), "Undo should be possible after saving the state.");
+ }
+
+ //=========== Filtered Person List Accessors Tests =============================================================
+
@Test
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
diff --git a/src/test/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicateTest.java
new file mode 100644
index 00000000000..cdef47eb3bb
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/DoctorNameContainsKeywordsPredicateTest.java
@@ -0,0 +1,91 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+public class DoctorNameContainsKeywordsPredicateTest {
+
+ @Test
+ public void equals() {
+ List firstPredicateKeywordList = Collections.singletonList("first");
+ List secondPredicateKeywordList = Arrays.asList("first", "second");
+
+ DoctorNameContainsKeywordsPredicate firstPredicate =
+ new DoctorNameContainsKeywordsPredicate(firstPredicateKeywordList);
+ DoctorNameContainsKeywordsPredicate secondPredicate =
+ new DoctorNameContainsKeywordsPredicate(secondPredicateKeywordList);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ DoctorNameContainsKeywordsPredicate firstPredicateCopy =
+ new DoctorNameContainsKeywordsPredicate(firstPredicateKeywordList);
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ // different types -> returns false
+ assertFalse(firstPredicate.equals(1));
+
+ // null -> returns false
+ assertFalse(firstPredicate.equals(null));
+
+ // different person -> returns false
+ assertFalse(firstPredicate.equals(secondPredicate));
+ }
+
+ @Test
+ public void test_nameContainsKeywords_returnsTrue() {
+ // One keyword
+ DoctorNameContainsKeywordsPredicate predicate =
+ new DoctorNameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ assertTrue(predicate.test(new PersonBuilder().withDoctorName("Alice Bob").build()));
+
+ // Multiple keywords
+ predicate = new DoctorNameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"));
+ assertTrue(predicate.test(new PersonBuilder().withDoctorName("Alice Bob").build()));
+
+ // Only one matching keyword
+ predicate = new DoctorNameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol"));
+ assertTrue(predicate.test(new PersonBuilder().withDoctorName("Alice Carol").build()));
+
+ // Mixed-case keywords
+ predicate = new DoctorNameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB"));
+ assertTrue(predicate.test(new PersonBuilder().withDoctorName("Alice Bob").build()));
+ }
+
+ @Test
+ public void test_nameDoesNotContainKeywords_returnsFalse() {
+ // Zero keywords
+ DoctorNameContainsKeywordsPredicate predicate =
+ new DoctorNameContainsKeywordsPredicate(Collections.emptyList());
+ assertFalse(predicate.test(new PersonBuilder().withDoctorName("Alice").build()));
+
+ // Non-matching keyword
+ predicate = new DoctorNameContainsKeywordsPredicate(Arrays.asList("Carol"));
+ assertFalse(predicate.test(new PersonBuilder().withDoctorName("Alice Bob").build()));
+
+ // Keywords match phone, email and address, but does not match name
+ predicate = new DoctorNameContainsKeywordsPredicate(Arrays.asList(
+ "12345", "alice@email.com", "Main", "Street"));
+ assertFalse(predicate.test(new PersonBuilder().withDoctorName("Alice").withPhone("12345")
+ .withEmail("alice@email.com").withAddress("Main Street").build()));
+ }
+
+ @Test
+ public void toStringMethod() {
+ List keywords = List.of("keyword1", "keyword2");
+ DoctorNameContainsKeywordsPredicate predicate = new DoctorNameContainsKeywordsPredicate(keywords);
+
+ String expected = DoctorNameContainsKeywordsPredicate.class.getCanonicalName() + "{keywords=" + keywords + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/DoctorNameTest.java b/src/test/java/seedu/address/model/person/DoctorNameTest.java
new file mode 100644
index 00000000000..14a60d04388
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/DoctorNameTest.java
@@ -0,0 +1,60 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class DoctorNameTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new DoctorName(null));
+ }
+
+ @Test
+ public void constructor_invalidName_throwsIllegalArgumentException() {
+ String invalidName = "";
+ assertThrows(IllegalArgumentException.class, () -> new DoctorName(invalidName));
+ }
+
+ @Test
+ public void isValidName() {
+ // null name
+ assertThrows(NullPointerException.class, () -> DoctorName.isValidName(null));
+
+ // invalid name
+ assertFalse(DoctorName.isValidName("")); // empty string
+ assertFalse(DoctorName.isValidName(" ")); // spaces only
+ assertFalse(DoctorName.isValidName("^")); // only non-alphanumeric characters
+ assertFalse(DoctorName.isValidName("peter*")); // contains non-alphanumeric characters
+
+ // valid name
+ assertTrue(DoctorName.isValidName("peter jack")); // alphabets only
+ assertTrue(DoctorName.isValidName("12345")); // numbers only
+ assertTrue(DoctorName.isValidName("peter the 2nd")); // alphanumeric characters
+ assertTrue(DoctorName.isValidName("Capital Tan")); // with capital letters
+ assertTrue(DoctorName.isValidName("David Roger Jackson Ray Jr 2nd")); // long names
+ }
+
+ @Test
+ public void equals() {
+ DoctorName name = new DoctorName("Valid Name");
+
+ // same values -> returns true
+ assertTrue(name.equals(new DoctorName("Valid Name")));
+
+ // same object -> returns true
+ assertTrue(name.equals(name));
+
+ // null -> returns false
+ assertFalse(name.equals(null));
+
+ // different types -> returns false
+ assertFalse(name.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(name.equals(new DoctorName("Other Valid Name")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/EmergencyContactTest.java b/src/test/java/seedu/address/model/person/EmergencyContactTest.java
new file mode 100644
index 00000000000..792b1a90557
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/EmergencyContactTest.java
@@ -0,0 +1,89 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BOB;
+
+import org.junit.jupiter.api.Test;
+
+public class EmergencyContactTest {
+ private static final EmergencyContact ALICE_EMERGENCY_CONTACT = ALICE.getFirstEmergencyContact();
+ private static final EmergencyContact BOB_EMERGENCY_CONTACT = BOB.getFirstEmergencyContact();
+ @Test
+ public void isSamePerson() {
+ // same object -> returns true
+ assertTrue(ALICE_EMERGENCY_CONTACT.isSamePerson(ALICE_EMERGENCY_CONTACT));
+
+ // null -> returns false
+ assertFalse(ALICE.isSamePerson(null));
+
+ // same phone, all other attributes different -> returns true
+ EmergencyContact editedAlice = new EmergencyContact(new Name(VALID_NAME_BOB),
+ ALICE_EMERGENCY_CONTACT.getPhone(),
+ new Relationship(VALID_ECRS_BOB));
+ assertTrue(ALICE_EMERGENCY_CONTACT.isSamePerson(editedAlice));
+
+ // different phone, all other attributes same -> returns false
+ editedAlice = new EmergencyContact(ALICE_EMERGENCY_CONTACT.getName(),
+ new Phone(VALID_PHONE_BOB),
+ ALICE_EMERGENCY_CONTACT.getRelationship());
+ assertFalse(ALICE_EMERGENCY_CONTACT.isSamePerson(editedAlice));
+
+ // name differs in case, all other attributes same -> returns true
+ EmergencyContact editedBob = new EmergencyContact(
+ new Name(BOB_EMERGENCY_CONTACT.getName().fullName.toLowerCase()),
+ BOB_EMERGENCY_CONTACT.getPhone(),
+ BOB_EMERGENCY_CONTACT.getRelationship());
+ assertTrue(BOB_EMERGENCY_CONTACT.isSamePerson(editedBob));
+ }
+
+ @Test
+ public void equals() {
+ // same values -> returns true
+ EmergencyContact aliceEmergencyContactCopy = new EmergencyContact(
+ ALICE_EMERGENCY_CONTACT.getName(), ALICE_EMERGENCY_CONTACT.getPhone(),
+ ALICE_EMERGENCY_CONTACT.getRelationship());
+ assertTrue(ALICE_EMERGENCY_CONTACT.equals(aliceEmergencyContactCopy));
+
+ // same object -> returns true
+ assertTrue(ALICE_EMERGENCY_CONTACT.equals(ALICE_EMERGENCY_CONTACT));
+
+ // null -> returns false
+ assertFalse(ALICE_EMERGENCY_CONTACT.equals(null));
+
+ // different type -> returns false
+ assertFalse(ALICE_EMERGENCY_CONTACT.equals(5));
+
+ // different person -> returns false
+ assertFalse(ALICE_EMERGENCY_CONTACT.equals(BOB_EMERGENCY_CONTACT));
+
+ // different name -> returns false
+ EmergencyContact editedAliceEmergencyContact = new EmergencyContact(
+ BOB_EMERGENCY_CONTACT.getName(),
+ ALICE_EMERGENCY_CONTACT.getPhone(),
+ ALICE_EMERGENCY_CONTACT.getRelationship()
+ );
+ assertFalse(ALICE.equals(editedAliceEmergencyContact));
+
+ // different phone -> returns false
+ editedAliceEmergencyContact = new EmergencyContact(
+ ALICE_EMERGENCY_CONTACT.getName(),
+ BOB_EMERGENCY_CONTACT.getPhone(),
+ ALICE_EMERGENCY_CONTACT.getRelationship()
+ );
+ assertFalse(ALICE.equals(editedAliceEmergencyContact));
+
+ // different relationship -> returns false
+ editedAliceEmergencyContact = new EmergencyContact(
+ ALICE_EMERGENCY_CONTACT.getName(),
+ ALICE_EMERGENCY_CONTACT.getPhone(),
+ BOB_EMERGENCY_CONTACT.getRelationship()
+ );
+ assertFalse(ALICE.equals(editedAliceEmergencyContact));
+
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java
index 94e3dd726bd..1f8578bbc59 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/person/NameTest.java
@@ -32,6 +32,8 @@ public void isValidName() {
// valid name
assertTrue(Name.isValidName("peter jack")); // alphabets only
+ assertTrue(Name.isValidName("peter jack jr.")); // dots included
+ assertTrue(Name.isValidName("peter jack-jean")); // hyphens included
assertTrue(Name.isValidName("12345")); // numbers only
assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters
assertTrue(Name.isValidName("Capital Tan")); // with capital letters
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..f84f8bcb897 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -14,6 +14,8 @@
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.person.exceptions.EmergencyContactNotFoundException;
import seedu.address.testutil.PersonBuilder;
public class PersonTest {
@@ -32,23 +34,18 @@ public void isSamePerson() {
// null -> returns false
assertFalse(ALICE.isSamePerson(null));
- // same name, all other attributes different -> returns true
- Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
+ // same phone, all other attributes different -> returns true
+ Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).withEmail(VALID_EMAIL_BOB)
.withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
assertTrue(ALICE.isSamePerson(editedAlice));
- // different name, all other attributes same -> returns false
- editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
+ // different phone, all other attributes same -> returns false
+ editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
assertFalse(ALICE.isSamePerson(editedAlice));
- // name differs in case, all other attributes same -> returns false
+ // name differs in case, all other attributes same -> returns true
Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build();
- assertFalse(BOB.isSamePerson(editedBob));
-
- // name has trailing spaces, all other attributes same -> returns false
- String nameWithTrailingSpaces = VALID_NAME_BOB + " ";
- editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build();
- assertFalse(BOB.isSamePerson(editedBob));
+ assertTrue(BOB.isSamePerson(editedBob));
}
@Test
@@ -93,7 +90,16 @@ public void equals() {
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", emergency contacts="
+ + ALICE.getEmergencyContacts().toString() + ", doctor=" + ALICE.getDoctor().toString()
+ + ", tags=" + ALICE.getTags() + "}";
assertEquals(expected, ALICE.toString());
}
+
+ @Test
+ public void invalid_emergencyContactIndex_throwsEmergencyContactNotFoundException() {
+ Person person = new PersonBuilder().build();
+ assertThrows(EmergencyContactNotFoundException.class, () ->
+ person.getEmergencyContact(Index.fromOneBased(10)));
+ }
}
diff --git a/src/test/java/seedu/address/model/person/RelationshipTest.java b/src/test/java/seedu/address/model/person/RelationshipTest.java
new file mode 100644
index 00000000000..8aaa46bc706
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/RelationshipTest.java
@@ -0,0 +1,76 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class RelationshipTest {
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Relationship(null));
+ }
+
+ @Test
+ public void constructor_invalidRelationship_throwsIllegalArgumentException() {
+ String invalidRelationship = "";
+ assertThrows(IllegalArgumentException.class, () -> new Relationship(invalidRelationship));
+ }
+
+ @Test
+ public void isAlphanumericRelationship() {
+ // null relationship
+ assertThrows(NullPointerException.class, () -> Relationship.isAlphanumericRelationship(null));
+
+ // invalid relationship
+ assertFalse(Relationship.isAlphanumericRelationship("")); // empty string
+ assertFalse(Relationship.isAlphanumericRelationship(" ")); // spaces only
+ assertFalse(Relationship.isAlphanumericRelationship("^")); // only non-alphanumeric characters
+ assertFalse(Relationship.isAlphanumericRelationship("peter*")); // contains non-alphanumeric characters
+
+ // valid relationship
+ assertTrue(Relationship.isAlphanumericRelationship("peter jack")); // alphabets only
+ assertTrue(Relationship.isAlphanumericRelationship("12345")); // numbers only
+ assertTrue(Relationship.isAlphanumericRelationship("peter the 2nd")); // alphanumeric characters
+ assertTrue(Relationship.isAlphanumericRelationship("Capital Tan")); // with capital letters
+ assertTrue(Relationship.isAlphanumericRelationship("David Roger Jackson Ray Jr 2nd")); // long names
+ }
+
+ @Test
+ public void isValidRelationship() {
+
+ // invalid relationship
+ assertFalse(Relationship.isValidRelationship("Tutor"));
+ assertFalse(Relationship.isValidRelationship("teacher"));
+ assertFalse(Relationship.isValidRelationship("baby"));
+ assertFalse(Relationship.isValidRelationship("neighbour"));
+
+ // valid relationship
+ assertTrue(Relationship.isValidRelationship("Son")); // alphabets only
+ assertTrue(Relationship.isValidRelationship("Daughter")); // numbers only
+ assertTrue(Relationship.isValidRelationship("Grandson")); // alphanumeric characters
+ assertTrue(Relationship.isValidRelationship("Granddaughter")); // with capital letters
+ assertTrue(Relationship.isValidRelationship("Child")); // long names
+ }
+
+ @Test
+ public void equals() {
+ Relationship relationship = new Relationship("Sibling");
+
+ // same values -> returns true
+ assertTrue(relationship.equals(new Relationship("Sibling")));
+
+ // same object -> returns true
+ assertTrue(relationship.equals(relationship));
+
+ // null -> returns false
+ assertFalse(relationship.equals(null));
+
+ // different types -> returns false
+ assertFalse(relationship.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(relationship.equals(new Relationship("Cousin")));
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedEmergencyContactTest.java b/src/test/java/seedu/address/storage/JsonAdaptedEmergencyContactTest.java
new file mode 100644
index 00000000000..1ed5db60c45
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonAdaptedEmergencyContactTest.java
@@ -0,0 +1,87 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalPersons.BENSON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.EmergencyContact;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
+
+public class JsonAdaptedEmergencyContactTest {
+ private static final String INVALID_ECNAME = "R#chel";
+ private static final String INVALID_ECPHONE = "+651234";
+ private static final String INVALID_ECRS = "Pet";
+ private static final String INVALID_ECRS_CHARACTERS = "@s5!ho";
+ private static final String VALID_ECNAME = BENSON.getFirstEmergencyContact().getName().toString();
+ private static final String VALID_ECPHONE = BENSON.getFirstEmergencyContact().getPhone().toString();
+ private static final String VALID_ECRS = BENSON.getFirstEmergencyContact().getRelationship().toString();
+
+ @Test
+ public void toModelType_validEmergencyContactDetails_returnsEmergencyContact() throws Exception {
+ EmergencyContact emergencyContact = BENSON.getFirstEmergencyContact();
+ JsonAdaptedEmergencyContact jsonAdaptedEmergencyContact = new JsonAdaptedEmergencyContact(emergencyContact);
+ assertEquals(jsonAdaptedEmergencyContact.toModelType(),
+ emergencyContact);
+ }
+
+ @Test
+ public void toModelType_invalidName_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(INVALID_ECNAME, VALID_ECPHONE, VALID_ECRS);
+ String expectedMessage = Name.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullName_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact person = new JsonAdaptedEmergencyContact(null, VALID_ECPHONE, VALID_ECRS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidPhone_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(VALID_ECNAME, INVALID_ECPHONE, VALID_ECRS);
+ String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullPhone_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(VALID_ECNAME, null, VALID_ECRS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidRelationship_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(VALID_ECNAME, VALID_ECPHONE, INVALID_ECRS);
+ String expectedMessage = Relationship.RELATIONSHIP_TYPE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidAlphanumericRelationship_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(VALID_ECNAME, VALID_ECPHONE, INVALID_ECRS_CHARACTERS);
+ String expectedMessage = Relationship.ALPHANUMERIC_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullRelationship_throwsIllegalValueException() {
+ JsonAdaptedEmergencyContact emergencyContact =
+ new JsonAdaptedEmergencyContact(VALID_ECNAME, VALID_ECPHONE, null);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Relationship.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, emergencyContact::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..0540ac05af9 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -14,23 +14,39 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
-
public class JsonAdaptedPersonTest {
- private static final String INVALID_NAME = "R@chel";
+ private static final String INVALID_NAME = "R#chel";
private static final String INVALID_PHONE = "+651234";
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
+ private static final String INVALID_ECNAME = "K0m4!";
+ private static final String INVALID_ECPHONE = "Q@+4567";
+ private static final String INVALID_ECRS = "M0th@r";
private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_DOCTOR_NAME = "J+hn Doe";
+ private static final String INVALID_DOCTOR_PHONE = "+123456";
+ private static final String INVALID_DOCTOR_EMAIL = "johndoe.com";
private static final String VALID_NAME = BENSON.getName().toString();
private static final String VALID_PHONE = BENSON.getPhone().toString();
private static final String VALID_EMAIL = BENSON.getEmail().toString();
private static final String VALID_ADDRESS = BENSON.getAddress().toString();
+ private static final String VALID_ECNAME = BENSON.getFirstEmergencyContact().getName().toString();
+ private static final String VALID_ECPHONE = BENSON.getFirstEmergencyContact().getPhone().toString();
+ private static final String VALID_ECRS = BENSON.getFirstEmergencyContact().getRelationship().toString();
+ private static final String VALID_DOCTOR_NAME = BENSON.getDoctor().getName().toString();
+ private static final String VALID_DOCTOR_PHONE = BENSON.getDoctor().getPhone().toString();
+ private static final String VALID_DOCTOR_EMAIL = BENSON.getDoctor().getEmail().toString();
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
+ private static final List VALID_EMERGENCY_CONTACTS =
+ BENSON.getEmergencyContacts().stream()
+ .map(JsonAdaptedEmergencyContact::new)
+ .collect(Collectors.toList());
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@@ -41,14 +57,20 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +78,21 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null,
+ VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +100,20 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null,
+ VALID_ADDRESS, VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,14 +121,20 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -103,8 +144,94 @@ public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ invalidTags);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullEmergencyContacts_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ null,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EmergencyContact.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidEmergencyContactName_throwsIllegalValueException() {
+ List invalidEmergencyContacts = new ArrayList<>();
+ invalidEmergencyContacts.add(new JsonAdaptedEmergencyContact(INVALID_ECNAME, VALID_ECPHONE, VALID_ECRS));
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ invalidEmergencyContacts,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidEmergencyContactPhone_throwsIllegalValueException() {
+ List invalidEmergencyContacts = new ArrayList<>();
+ invalidEmergencyContacts.add(new JsonAdaptedEmergencyContact(VALID_ECNAME, INVALID_ECPHONE, VALID_ECRS));
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ invalidEmergencyContacts,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
assertThrows(IllegalValueException.class, person::toModelType);
}
+ @Test
+ public void toModelType_invalidEmergencyContactRelationship_throwsIllegalValueException() {
+ List invalidEmergencyContacts = new ArrayList<>();
+ invalidEmergencyContacts.add(new JsonAdaptedEmergencyContact(VALID_ECNAME, VALID_ECPHONE, INVALID_ECRS));
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ invalidEmergencyContacts,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullDoctor_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ null, VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDoctorName_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(INVALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDoctorPhone_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, INVALID_DOCTOR_PHONE, VALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDoctorEmail_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_EMERGENCY_CONTACTS,
+ new JsonAdaptedDoctor(VALID_DOCTOR_NAME, VALID_DOCTOR_PHONE, INVALID_DOCTOR_EMAIL),
+ VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..faa294acc87 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -1,15 +1,20 @@
package seedu.address.testutil;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.person.Address;
+import seedu.address.model.person.DoctorName;
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.person.Relationship;
import seedu.address.model.tag.Tag;
/**
@@ -36,6 +41,13 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
descriptor.setAddress(person.getAddress());
+ descriptor.setIndexOfEmergencyContactToEdit(INDEX_FIRST_PERSON);
+ descriptor.setEmergencyContactName(person.getFirstEmergencyContact().getName());
+ descriptor.setEmergencyContactPhone(person.getFirstEmergencyContact().getPhone());
+ descriptor.setEmergencyContactRelationship(person.getFirstEmergencyContact().getRelationship());
+ descriptor.setDoctorName(person.getDoctor().getName());
+ descriptor.setDoctorPhone(person.getDoctor().getPhone());
+ descriptor.setDoctorEmail(person.getDoctor().getEmail());
descriptor.setTags(person.getTags());
}
@@ -71,6 +83,62 @@ public EditPersonDescriptorBuilder withAddress(String address) {
return this;
}
+ /**
+ * Sets the {@code EmergencyContact Index} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEmergencyContactIndex(Index index) {
+ descriptor.setIndexOfEmergencyContactToEdit(index);
+ return this;
+ }
+
+ /**
+ * Sets the {@code EmergencyContact Name} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEmergencyContactName(String name) {
+ descriptor.setEmergencyContactName(new Name(name));
+ return this;
+ }
+
+ /**
+ * Sets the {@code EmergencyContact Phone} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEmergencyContactPhone(String phone) {
+ descriptor.setEmergencyContactPhone(new Phone(phone));
+ return this;
+ }
+
+ /**
+ * Sets the {@code EmergencyContact relationship} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEmergencyContactRelationship(String relationship) {
+ descriptor.setEmergencyContactRelationship(new Relationship(relationship));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Name} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withDoctorName(String name) {
+ descriptor.setDoctorName(new DoctorName(name));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Phone} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withDoctorPhone(String phone) {
+ descriptor.setDoctorPhone(new Phone(phone));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Email} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withDoctorEmail(String email) {
+ descriptor.setDoctorEmail(new Email(email));
+ return this;
+ }
+
/**
* Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..0f79826204a 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -1,13 +1,20 @@
package seedu.address.testutil;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Doctor;
+import seedu.address.model.person.DoctorName;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmergencyContact;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Relationship;
import seedu.address.model.tag.Tag;
import seedu.address.model.util.SampleDataUtil;
@@ -20,21 +27,34 @@ public class PersonBuilder {
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_ECNAME = "Sarah Lim";
+ public static final String DEFAULT_ECPHONE = "85541325";
+ public static final String DEFAULT_ECRS = "Parent";
+ public static final String DEFAULT_DOC_NAME = "John Doe";
+ public static final String DEFAULT_DOC_PHONE = "98927134";
+ public static final String DEFAULT_DOC_EMAIL = "johndoe@gmail.com";
private Name name;
private Phone phone;
private Email email;
private Address address;
+ private Set emergencyContacts;
+ private Doctor doctor;
private Set tags;
/**
- * Creates a {@code PersonBuilder} with the default details.
+ * Creates a {@code PersonBuilder} with the default details. The person has only one emergency contact
*/
public PersonBuilder() {
name = new Name(DEFAULT_NAME);
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
+ EmergencyContact emergencyContact = new EmergencyContact(new Name(DEFAULT_ECNAME),
+ new Phone(DEFAULT_ECPHONE), new Relationship(DEFAULT_ECRS));
+ emergencyContacts = new LinkedHashSet<>(Arrays.asList(emergencyContact));
+ doctor = new Doctor(new DoctorName(DEFAULT_DOC_NAME), new Phone(DEFAULT_DOC_PHONE),
+ new Email(DEFAULT_DOC_EMAIL));
tags = new HashSet<>();
}
@@ -46,6 +66,8 @@ public PersonBuilder(Person personToCopy) {
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
address = personToCopy.getAddress();
+ emergencyContacts = personToCopy.getEmergencyContacts();
+ doctor = personToCopy.getDoctor();
tags = new HashSet<>(personToCopy.getTags());
}
@@ -58,9 +80,10 @@ public PersonBuilder withName(String name) {
}
/**
- * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
+ * Parses the {@code tags} into a {@code Set} and set it to the
+ * {@code Person} that we are building.
*/
- public PersonBuilder withTags(String ... tags) {
+ public PersonBuilder withTags(String... tags) {
this.tags = SampleDataUtil.getTagSet(tags);
return this;
}
@@ -89,8 +112,60 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code EmergencyContact} of the {@code Person} that we are
+ * building.
+ */
+ public PersonBuilder withEmergencyContact(String ecName, String ecPhone, String ecrs) {
+ EmergencyContact emergencyContact = new EmergencyContact(new Name(ecName), new Phone(ecPhone),
+ new Relationship(ecrs));
+ this.emergencyContacts = new LinkedHashSet<>(List.of(emergencyContact));
+ return this;
+ }
+
+ /**
+ * Adds an {@code EmergencyContact} to the {@code Person} that we are
+ * building.
+ */
+ public PersonBuilder addEmergencyContact(String ecName, String ecPhone, String ecrs) {
+ EmergencyContact emergencyContact = new EmergencyContact(new Name(ecName), new Phone(ecPhone),
+ new Relationship(ecrs));
+ Set emergencyContactSet = new LinkedHashSet<>(this.emergencyContacts);
+ emergencyContactSet.add(emergencyContact);
+ this.emergencyContacts = emergencyContactSet;
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Name} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withDoctorName(String doctorName) {
+ this.doctor = new Doctor(new DoctorName(doctorName), doctor.getPhone(), doctor.getEmail());
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Phone} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withDoctorPhone(String doctorPhone) {
+ this.doctor = new Doctor(doctor.getName(), new Phone(doctorPhone), doctor.getEmail());
+ return this;
+ }
+
+ /**
+ * Sets the {@code Doctor Email} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withDoctorEmail(String doctorEmail) {
+ this.doctor = new Doctor(doctor.getName(), doctor.getPhone(), new Email(doctorEmail));
+ return this;
+ }
+
+ /**
+ * Builds a {@code Person} based on the fields set.
+ */
public Person build() {
- return new Person(name, phone, email, address, tags);
+ assert emergencyContacts.size() > 0;
+ return new Person(name, phone, email, address, emergencyContacts, doctor, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..d9fc44384cb 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,7 +1,14 @@
package seedu.address.testutil;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DOC_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_RELATIONSHIP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT_TO_EDIT;
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;
@@ -27,6 +34,7 @@ public static String getAddCommand(Person person) {
/**
* Returns the part of command string for the given {@code person}'s details.
+ * Take note that this method is only applicable when the person has only one emergency contact
*/
public static String getPersonDetails(Person person) {
StringBuilder sb = new StringBuilder();
@@ -34,6 +42,14 @@ public static String getPersonDetails(Person person) {
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
+ sb.append(PREFIX_EMERGENCY_CONTACT_NAME + person.getFirstEmergencyContact().getName().fullName + " ");
+ sb.append(PREFIX_EMERGENCY_CONTACT_PHONE + person.getFirstEmergencyContact().getPhone().value + " ");
+ sb.append(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP
+ + person.getFirstEmergencyContact().getRelationship().relationship + " ");
+ sb.append(PREFIX_DOC_NAME + person.getDoctor().getName().fullName + " ");
+ sb.append(PREFIX_DOC_PHONE + person.getDoctor().getPhone().value + " ");
+ sb.append(PREFIX_DOC_EMAIL + person.getDoctor().getEmail().value + " ");
+
person.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
@@ -49,6 +65,22 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
+ descriptor.getIndexOfEmergencyContactToEdit().ifPresent(indexOfEmergencyContactToEdit ->
+ sb.append(PREFIX_EMERGENCY_CONTACT_TO_EDIT).append(indexOfEmergencyContactToEdit.getOneBased())
+ .append(" "));
+ descriptor.getEmergencyContactName().ifPresent(emergencyContactName ->
+ sb.append(PREFIX_EMERGENCY_CONTACT_NAME).append(emergencyContactName.fullName).append(" "));
+ descriptor.getEmergencyContactPhone().ifPresent(emergencyContactPhone ->
+ sb.append(PREFIX_EMERGENCY_CONTACT_PHONE).append(emergencyContactPhone.value).append(" "));
+ descriptor.getEmergencyContactRelationship().ifPresent(emergencyContactRelationship ->
+ sb.append(PREFIX_EMERGENCY_CONTACT_RELATIONSHIP)
+ .append(emergencyContactRelationship.relationship).append(" "));
+ descriptor.getDoctorName().ifPresent(doctorName ->
+ sb.append(PREFIX_DOC_NAME).append(doctorName.fullName).append(" "));
+ descriptor.getDoctorPhone().ifPresent(doctorPhone ->
+ sb.append(PREFIX_DOC_PHONE).append(doctorPhone.value).append(" "));
+ descriptor.getDoctorEmail().ifPresent(doctorEmail ->
+ sb.append(PREFIX_DOC_EMAIL).append(doctorEmail.value).append(" "));
if (descriptor.getTags().isPresent()) {
Set tags = descriptor.getTags().get();
if (tags.isEmpty()) {
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..dd16576afb7 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -2,6 +2,18 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_EMAIL_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DOC_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECNAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECPHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ECRS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
@@ -25,21 +37,37 @@ public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
- .withPhone("94351253")
+ .withPhone("94351253").withEmergencyContact("Sarah Lim", "98761234", "Parent")
+ .withDoctorName("Tan Wei Ming").withDoctorPhone("82345678").withDoctorEmail("tanweiming@gmail.com")
.withTags("friends").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("johnd@example.com").withPhone("98765432")
+ .withEmergencyContact("Kevin Goh", "98764123", "Husband")
+ .withDoctorName("Koh Jia Jia").withDoctorPhone("81818211").withDoctorEmail("jjkoh@hotmail.com")
.withTags("owesMoney", "friends").build();
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
+ .withEmail("heinz@example.com").withAddress("wall street")
+ .withEmergencyContact("Kevin Goh", "98764123", "Husband")
+ .withDoctorName("Ben Po").withDoctorPhone("61231231").withDoctorEmail("benpo@gmail.com")
+ .build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
+ .withEmail("cornelia@example.com").withAddress("10th street")
+ .withEmergencyContact("Haziq Bin Abudllah", "98763412", "Brother")
+ .withDoctorName("Sebastian Sim").withDoctorPhone("62353535").withDoctorEmail("sebsim@hotmail.com")
+ .withTags("friends").build();
public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
+ .withEmergencyContact("Amanda Lee", "98762341", "Cousin")
+ .withDoctorName("Wong See Toh").withDoctorPhone("88338483").withDoctorEmail("wongst@hotmail.com")
.withEmail("werner@example.com").withAddress("michegan ave").build();
public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
+ .withEmergencyContact("Nurul Ain", "98761243", "Grandmother")
+ .withDoctorName("Bing Qi Ling").withDoctorPhone("81818211").withDoctorEmail("bingqiling@hotmail.com")
.withEmail("lydia@example.com").withAddress("little tokyo").build();
public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
+ .withEmergencyContact("Anjali Devi", "98763124", "Daughter")
+ .addEmergencyContact("Buddy Hoagies", "64815432", "Son")
+ .withDoctorName("Peh Boon Hao").withDoctorPhone("96161616").withDoctorEmail("boonhao@hotmail.com")
.withEmail("anna@example.com").withAddress("4th street").build();
// Manually added
@@ -50,9 +78,17 @@ public class TypicalPersons {
// Manually added - Person's details found in {@code CommandTestUtil}
public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withEmergencyContact(VALID_ECNAME_AMY, VALID_ECPHONE_AMY, VALID_ECRS_AMY)
+ .withDoctorName(VALID_DOC_NAME_AMY).withDoctorPhone(VALID_DOC_PHONE_AMY)
+ .withDoctorEmail(VALID_DOC_EMAIL_AMY)
+ .withTags(VALID_TAG_FRIEND).build();
public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withEmergencyContact(VALID_ECNAME_BOB, VALID_ECPHONE_BOB, VALID_ECRS_BOB)
+ .withDoctorName(VALID_DOC_NAME_BOB).withDoctorPhone(VALID_DOC_PHONE_BOB)
+ .withDoctorEmail(VALID_DOC_EMAIL_BOB)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
.build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER