diff --git a/.gitignore b/.gitignore index 71c9194e8bd..8f089a2d83d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ +bin/ diff --git a/README.md b/README.md index 13f5c77403f..f301282e1c1 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# _Healthcare Xpress_ [![Java CI](https://github.com/AY2223S1-CS2103-F13-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S1-CS2103-F13-4/tp/actions/workflows/gradle.yml) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
+ +* This is an **address log application for medical staff**.
Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. + * for medical administrators to lookup patient details and forward them to the relevant attending nurses or physicians + * for medical administrators to keep track of which nurses are assigned to which patients + * for medical administrators to label patients with specific conditions so precautions can be taken by attending staff if needed +* As most commands are inputed with the keyboard, it is best suited for a medical administrator who is familiar with CLI Applications. +* However, due to its intuitive design, beginners can expect to pick it up very quickly. +* For the detailed documentation of this project, see the **[Healthcare Xpress Product Website](https://ay2223s1-cs2103-f13-4.github.io/tp/)**. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index 108397716bd..78146e95ed9 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,10 @@ repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } +run { + enableAssertions = true +} + checkstyle { toolVersion = '10.2' } @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'healthcarexpress.jar' } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..60b95272df5 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### John Benedict Yan - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/johnbenedictyan)] +[[portfolio](team/johnbenedictyan.md)] -* Role: Project Advisor +* Role: Team Lead and Code Quality In-Charge +* Responsibilities: Commons -### Jane Doe +### Phoong Xiang Han - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/xhphoong)] +[[portfolio](team/xhphoong.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Documentation In-Charge +* Responsibilities: Logic -### Johnny Doe +### Yee Hao - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/yeehaoo)] [[portfolio](team/yeehaoo.md)] -* Role: Developer -* Responsibilities: Data +* Role: Deliverables and deadlines, and Scheduling and tracking In-Charge +* Responsibilities: Model -### Jean Doe +### Malcolm Low - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/mlzt2000)] +[[portfolio](team/mlzt2000.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +* Role: Integration In-Charge +* Responsibilities: User Interface -### James Doe +### Lim Zhan Feng - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/lolfoollors)] +[[portfolio](team/lolfoollors.md)] -* Role: Developer -* Responsibilities: UI +* Role: Testing In-Charge +* Responsibilities: Storage diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..5af4bee7789 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,64 +2,60 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- +- Table of Contents + {:toc} -## **Acknowledgements** - -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} - --------------------------------------------------------------------------------------------------------------------- +--- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2223S1-CS2103-F13-4/tp/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +
### Architecture -The ***Architecture Diagram*** given above explains the high-level design of the App. +The **_Architecture Diagram_** given above explains the high-level design of the App. Given below is a quick overview of main components and how they interact with each other. **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup methods where necessary. +**`Main`** has two classes called [`Main`](https://github.com/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for, + +- At app launch: Initializes the components in the correct sequence, and connects them up with each other. +- At shut down: Shuts down the components and invokes cleanup methods where necessary. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. The rest of the App consists of four components. -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. - +- [**`UI`**](#ui-component): The UI of the App. +- [**`Logic`**](#logic-component): The command executor. +- [**`Model`**](#model-component): Holds the data of the App in memory. +- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. Each of the four main components (also shown in the diagram above), -* defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +- defines its _API_ in an `interface` with the same name as the Component. +- implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -69,31 +65,32 @@ 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/AY2223S1-CS2103-F13-4/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/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, -* executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +- 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/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. + +1. When `Logic` is called upon to execute a command, it uses the `HealthcareXpressParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. 1. The command can communicate with the `Model` when it is executed (e.g. to add a person). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. @@ -110,21 +107,26 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +- When called upon to parse a user command, the `HealthcareXpressParser` 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 `HealthcareXpressParser` 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/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/model/Model.java) + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -* stores 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) +- stores healthcareXpress data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +- stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +- stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. +- does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) + +A diagram of the `Person` class is shown below, for better understanding of the attributes and relations of the different types. + +
: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.
@@ -132,124 +134,353 @@ The `Model` component,
- ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2223S1-CS2103-F13-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) + +- can save both address book data and user preference data in json format, and read them back into corresponding objects. +- inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +- depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) ### Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. --------------------------------------------------------------------------------------------------------------------- +--- ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Unique ID Mechanism + +#### Motivation: + +Individuals who are modelled by the subclasses of `Person` may have very similar attributes, such as name, gender and tags. As such there is a high chance that the medical administrator may be confused when looking at similar persons. Thus, a unique id (UID) was introduced to act as the differentiator for such persons. -#### Proposed Implementation +#### Implementation for unique ID: -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: +The unique ID mechanism is facilitated by the `Uid` class. The `Uid` instance utilizes the `AtomicLong` class and ensures that unique `Uid` are used throughout the system. -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +Originally there was a `UidManager` class which facilitated the generation and implementation of ensuring that there is no repeated `Uid` objects. However, this was later refactored to utilize the `AtomicLong` class. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +### Add feature -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +#### Implementation for adding a patient: -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +The add patient mechanism is facilitated by `Patient`, `AddCommandParser`,`AddCommand`, `Model`, `AddressBook` and `UniquePersonList`. -![UndoRedoState0](images/UndoRedoState0.png) +`Patient` extends from `Person`. A `Patient` has the `Person` attributes and a `dateSlotList`. This is shown in the diagram below: -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. +![PatientClassDiagram](images/PatientClassDiagram.png) -![UndoRedoState1](images/UndoRedoState1.png) +The `AddCommandParser` takes in user input, extracts information and creates a `Patient`. -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +The `AddCommand` will then be executed, adding the `Patient` to the `Model`'s `AddressBook`'s `UniquePersonList`. -![UndoRedoState2](images/UndoRedoState2.png) +Given below is an example usage scenario and how the add patient mechanism behaves at each step. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +Step 1. The user executes `add c/P n/Lily g/F p/91103813 a/ABC STREET 111 e/lily@gmail.com t/heartDisease ds/2022-10-10,3` command to add a new patient that requires nurse's home-visit. + +
:information_source: **Note:** The `c/P` is needed to indicate that the person added is a patient.`Name`, `Gender`, `Phone`, `Address`, `Email` is compulsory but `Tag` and `DateTime` has been made optional. The `n/`, `g/`, `p/` ... are the prefixes used to extract different details' field of the patient.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 2. The `HealthcareXpressParser` will parse the user command to return an `AddCommandParser` with the patient's details. + +Step 3. The `AddCommandParser` will parse the respective patient's details using fixed prefixes and check their validity. The `Uid` for the patient will also be generated and used along with the parsed patient's details to create a patient if all the inputs are valid. Then, it returns an `AddCommand` with the patient created. + +Step 4. The `AddCommand` will be executed and the patient will be added to the `Model`'s `AddressBook`'s `UniquePersonList`. In the `UniquePersonList`, potential duplication will be checked. -![UndoRedoState3](images/UndoRedoState3.png) +The following sequence diagram shows how the add patient operation works: -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +
:information_source: **Note:** For simplification purpose, `c/P n/Lily g/F p/91103813 a/ABC STREET 111 e/lily@gmail.com t/heartDisease ds/2022-10-10,3` will be written as userInput and all the parsed patient's details will be written as patientDetails.
-The following sequence diagram shows how the undo operation works: +![AddSequenceDiagram](images/AddSequenceDiagram.png) -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +Step 5. The `LogicManager` will then call `saveAddressBook()` to store the new updated `AddressBook` so that the data of the new patient can be retrieved later. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +The following activity diagram summarizes what happens when a user executes an add patient command: -
+![AddPatientActivityDiagram](images/AddPatientActivityDiagram.png) -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +#### Design considerations: -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +**Aspect: How to deal with duplication:** -
+- **Alternative 1:** Check the name. If the name is the same, then show duplicate error and do not proceed to add the patient. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. + - Pros: Easy to implement. + - Cons: If the 2 different patients have the exact same name, the user would not be able to add that patient. -![UndoRedoState4](images/UndoRedoState4.png) +- **Alternative 2:** Check the name. If the name is the same, then show duplicate warning but that patient would still be added. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. + - Pros: If the 2 different patients have the exact same name, the user would still be able to add that patient. At the same time, it will show potential duplication to the user. + - Cons: The user have to manually check whether it is the same person and delete it if it is a duplication. + - Cons: The user might miss out the duplicated patients. -![UndoRedoState5](images/UndoRedoState5.png) +- **Alternative 3 (Chosen):** Check the similarity levels by comparing attributes of both people. If there is only a zero or one field difference between the two, then show duplicate warning but that patient would still be added + - Pros: By comparing similarity levels, it reduces the chances of actually having a duplicate, which means that if the warning appears then there is a high chance that it is an actual duplicate, not a false duplicate with only same name. + - Cons: The user would still have to manually delete if it is a duplicate. -The following activity diagram summarizes what happens when a user executes a new command: +**Aspect: The home-visit `DateTime` input:** - +- **Alternative 1:** The `DateTime` input is in the format of `YYYY-MM-DDTHH:mm` and it can in any time. + + - Pros: More specific date and time recorded for the patient. + - Pros: More flexible in the home visit date and time that a patient can choose. + - Cons: It is hard to determine/check time crashes when assigning a home-visit `DateTime` to a nurse. + +- **Alternative 2 (Chosen):** The `DateTime` input will be in the format of `YYYY-MM-DD` and slot. The slot will have fixed starting time and fixed duration. + - Pros: It is easy to determine/check time crashes when assigning a home-visit `DateTime` slot to a nurse. + - Cons: Less flexible in the home visit date and time that a patient can choose. + +### Unmark feature + +#### Implementation for unmarking home visits between Nurses and Patients + +The unmarking mechanism is facilitated by `Patient`, `Nurse`, `UnmarkCommandParser`, `UnmarkCommand`, `Model`, `AddressBook`, and `UniquePersonList`. + +The `HealthcareXpressParser` will take in user input and recognise it as an `UnmarkCommand`, and pass on the user input to `UnmarkCommandParser`. + +`UnmarkCommandParser` will then identify the Patient of interest, by parsing the uid provided by the user. + +`UnmarkCommandParser` will also identify the dateslot index given by the user. + +`UnmarkCommandParser` returns an `UnmarkCommand`. + +Upon execution, the `UnmarkCommand` will check if the uid refers to a valid patient. If so, it will unmark the dateslot at the specified dateslot index. + +Given below is an example usage scenario and how the unmark mechanism works. + +Step 1. The user enters the command `unmark id/1 dsi/1` command to unmark the home visit at dateslot index 1, of the Patient with uid of 1. + +Step 2. The `HealthcareXpressParser` will parse the user command and pass the input to the `UnmarkCommandParser` + +Step 3. The `UnmarkCommandParser` will parse the uid and dateslot index, and ensure that both are present. It will then return an `UnmarkCommand` with the uid and dateslot index. + +Step 4. The `UnmarkCommand` will execute, creating an `InternalEditor` to update the dateslot list of the patient with the dateslot at the specified dateslot index unmarked. + +The following sequence diagram shows how unmarking a dateslot works: + +![UnmarkSequenceDiagram](images/UnmarkSequenceDiagram.png) + +The following activity diagram shows what happens when a user unmarks a dateslot. + +![UnmarkActivityDiagram](images/UnmarkActivityDiagram.png) #### Design considerations: -**Aspect: How undo & redo executes:** +** Aspect: Unmarking DateSlots that have not been marked: ** + +- **Alternative 1:** Print and error message to inform the user that the dateslot has not been marked. + - Pros: User will be made aware that they have probably erroneously unmarked the wrong dateslot, and make the necessary correction. + - Cons: More difficult to implement, requires more thorough testing. + +- **Alternative 2:** Make no changes and raise no exceptions. + - Pros: Easier to implement and test. + - Cons: User may have erroneously unmarked the wrong dateslot, and may not notice. + +### Undounmark feature + +#### Implementation for remarking dateslots of Patients + +The undo unmarking mechanism is facilitated by `Patient`, `Nurse`, `UndoUnmarkCommandParser`, `UndoUnmarkCommand`, `Model`, `AddressBook`, and `UniquePersonList`. + +The `HealthcareXpressParser` will take in user input and recognise it as an `UndoUnmarkCommand`, and pass on the user input to `UndoUnmarkCommandParser`. + +`UndoUnmarkCommandParser` will then identify the Patient of interest, by parsing the uid provided by the user. + +`UndoUnmarkCommandParser` will also identify the dateslot index given by the user. + +`UndoUnmarkCommandParser` returns an `UndoUnmarkCommand`. + +Upon execution, the `UndoUnmarkCommand` will check if the uid refers to a valid patient. If so, it will check if the dateslot at the specified dateslot is currently marked. If it is unmarked, it will undo the unmarking. + +Given below is an example usage scenario and how the undo unmark mechanism works. + +Step 1. The user enters the command `undounmark id/1 dsi/1` command to undo the unmarking of the dateslot at index 1 of the dateslot list of the Patient with uid of 1. + +Step 2. The `HealthcareXpressParser` will parse the user command and pass the input to the `UndoUnmarkCommandParser` + +Step 3. The `UndoUnmarkCommandParser` will parse the uid and dateslot index, and ensure that both are present. It will then return an `UndoUnmarkCommand` with the uid and dateslot index. + +Step 4. The `UndoUnmarkCommand` will execute, creating an `InternalEditor` to update the dateslot list of the patient with the dateslot at the specified dateslot index marked. + +The following sequence diagram shows how undoing an unmarking works: + +![UndoUnmarkSequenceDiagram](images/UndoUnmarkSequenceDiagram.png) + +The following activity diagram shows what happens when a user undos an unmark. + +![UndoUnmarkActivityDiagram](images/UndoUnmarkActivityDiagram.png) + +#### Design considerations + +** Aspect: Undo unmarking DateSlots that have already been marked: ** + +- **Alternative 1 (chosen):** Print an error message to inform the user that the dateslot has been marked. + - Pros: User will be made aware that they have probably erroneously marked the wrong dateslot, and make the necessary correction. + - Cons: More difficult to implement, requires more thorough testing. + +- **Alternative 2:** Make no changes and raise no exceptions. + - Pros: Easier to implement and test. + - Cons: User may have erroneously marked the wrong dateslot, and may not notice. + + +### List feature + +#### Implementation for listing patients and nurses based on specified criteria + +The list user function is primarily facilitated by `ListCommandParser`,`ListCommand`. `Model` and `Person` are also involved. + +`ListCommandParser` takes in user input and extracts the specified criteria that the user wants. + +The criteria are then passed to `ListCommand` which will create a `Predicate` based on the given criteria. + +This `Predicate` is passed to `Model`, which will filter and display the enrolled users who match the given criteria. + +Given below is an example usage scenario and how the list function behaves at each step. It is illustrated with the following sequence diagram: + +![ListSequenceDiagram](images/ListSequenceDiagram.png) + +Step 1. The user executes `list c/n g/f` to list all female nurses. + +Step 2. `HealthcareXpressParser` parses the user command to return a `ListCommandParser` with the given criteria. + +Step 3. The `ListCommandParser` parses the criteria using fixed prefixes and check their validity. Then, it returns an `ListCommand` with the criteria `category=N, gender=F`. + +Step 4. The `ListCommand` will be executed and a `Predicate` of `category=N, gender=F` is created and passed to `Model`. + +Step 5. `Model` applies the `Predicate` and filters the list of enrolled users, displaying all female nurses only. + +The following activity diagram summarizes what happens when a user executes the list command: + +![ListActivityDiagram](images/ListActivityDiagram.png) + +#### Design considerations: + +**Aspect: Dealing with one invalid input amoung multiple valid inputs:** + +- **Alternative 1 (Chosen):** Verify validity of all inputs. If an input is invalid, ignore it and list based on the other given inputs, giving a user warning. + + - Pros: It might be more convenient for the user in certain circumstances where exact criteria matching is not vital. + - Cons: The user might think that the returned list fits the given criteria exactly, which might lead to user errors. + +- **Alternative 2:** Verify validity of all inputs. If an input is invalid, do not process the command. + - Pros: If a list is returned then the user can be sure that all returned users match the given criteria exactly. + - Cons: Possibly inefficient if exact matching is not vital. + +### Update Emergency Contacts feature + +#### Implementation for updating attending physician and next of kin contact information for patients: + +The feature is primarily facilitated by `UpdateContactCommand` and `UpdateContactCommandParser`. + +`UpdateContactCommandParser` takes in user input, extracts contact info and passes it to `UpdateContactCommand`. + +`UpdateContactCommand` creates a new `NextOfKin` or `Physician`, based on whichever one the user specified. + +The new `NextOfKin` or `Physician` will have the contact details as stated by the user. + +`UpdateContactCommand` then gets the `Patient` from the database and edits the `Patient` to include the new contact. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +The new `Patient`, with the contact info, is then passed to `Model`, so that the details are saved in the database. -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +Given below is an example scenario and how the `UpdateContactCommand` behaves at each step, +illustrated with the following sequence diagram: -_{more aspects and alternatives to be added}_ +![UpdateContactSequenceDiagram](images/UpdateContactSequenceDiagram.png) -### \[Proposed\] Data archiving +Step 1. The user executes `updatecontact id/3 n/ John Doe p/ 81234567 e/ johndoe@example.com c/ D` -_{Explain here how the data archiving feature will be implemented}_ +Step 2. `HealthcareXpressParser` creates an `UpdateContactCommandParser` to parse the arguments. +Step 3. `UpdateContactCommandParser` checks validity of the given arguments and creates an `UpdateContactCommand`. --------------------------------------------------------------------------------------------------------------------- +Step 4. The `UpdateContactCommand` is executed, and a new `Physician` with the given contact info is created`. + +Step 5. `UpdateContactCommand` gets `Patient` with UID 3 from the database, and updates the `Patient` to contain +`Physician` John Doe. + +Step 6. `Model` updates the database, and displays the attending physician on `Patient` UID 3. + +The activity diagram below summarises exception handling of UpdateContactCommand: + +![UpdateContactActivityDiagram](images/UpdateContactActivityDiagram.png) + +### Assign Feature + +#### Motivation: + +- The assign feature is necessary so that the medical administrator can visually see which nurse is attending which patient's home visit. + +#### Implementation: + +![AssignSequenceDiagram](images/AssignSequenceDiagram.png) + +Step 1. The user executes `assign id/3 id/2` + +Step 2. `HealthcareXpressParser` creates an `AssignCommandParser` to parse the arguments. + +Step 3. `AssignCommandParser` checks validity of the given arguments and creates an `AssignCommand`. + +Step 4. The `AssignCommand` is executed, and a new `InternalEditor` is created. + +Step 5. `AssignCommand` calls the `InternalEditor`'s methods of `editPatient` and `editNurse`. + +Step 6. `Model` updates the database, and displays all the persons. + +The activity diagram below summarises exception handling of AssignCommand: + +![AssignActivityDiagram](images/AssignActivityDiagram.png) +
+ +#### Design considerations: + +- **Aspect: How the parse interprets the order of uids** + - **Alternative 1:** Fix the order of the uid, so patient then nurse + - Pros: There will be less checking needed to deduce the class of the persons involved. + - Cons: The user experience will suffer as the medical administrator might not be able to accurately remember which uid corresponding to which person, the nurse or the patient. +
+ - **Alternative 2:** Have no fix order, as long as one nurse uid and one patient uid is inputted + - Pros: The user experience will be better as there will be more leeway. + - Cons: Harder to implement and more testing is required. +
+ +### Deassign Feature + +#### Motivation: + +- The deassign feature is necessary so that the medical administrator deassign a nurse to a patient's home visit if a mistake has been made or changes are necessary. + +#### Implementation: +Step 1. The user executes `deassign id/3` + +Step 2. `HealthcareXpressParser` creates an `DeassignCommandParser` to parse the arguments. + +Step 3. `DeassignCommandParser` checks validity of the given arguments and creates an `DeassignCommand`. + +Step 4. The `DeassignCommand` is executed, the person with the id of 3 will have their home visits deassigned. + +Step 5. `Model` updates the database, and displays all the persons. +
## **Documentation, logging, testing, configuration, dev-ops** -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Requirements** @@ -257,73 +488,536 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps - -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +- medical administrator who has a need to manage a significant number of patients and nurses +- prefer desktop apps over other types +- can type fast with precision +- prefers typing to mouse interactions +- is reasonably comfortable using CLI apps +**Value proposition**: manage patient nurse relations faster than a typical mouse/GUI driven app ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| Priority | As a … | I can … | So that … | +| -------- | --------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `*` | medical administrator | export a current week’s schedule for nurses so that | they can check their schedules independently | +| `* * *` | medical administrator | quickly add the details of a patients that require home visits so that | retrieve them later | +| `* * *` | medical administrator | quickly add the details of the nurse | retrieve them later for matching | +| `* * *` | medical administrator | add the health conditions (eg heart disease, asthma, paralysed etc) of the patients as tags | use this information to match the patient with the respective nurse that has experience in dealing with such disease | +| `* *` | medical administrator | add a list of diseases that the nurse dealt with before as tags | give them the appropriate patient for a home visit | +| `* * *` | medical administrator | add patients to the existing list of patients that the nurse is going to home visit for the current week | use it to create the schedule for the nurse | +| `*` | medical administrator | add the availability of the nurse | use this information to assign the patient to them | +| `* * *` | medical administrator | add the date (and time + duration) of home visit appointments for the patient | schedule the nurses accordingly | +| `*` | medical administrator | add next of kin particulars for a patient | inform them in case of any emergency | +| `*` | medical administrator | add the contact details of the patient's attending physician | liaise with them regarding the patient's treatments and how the patient responds to them | +| `* *` | medical administrator | add patients' critical information | quickly identify any essential information that needs to be taken note of during scheduling | +| `*` | medical administrator | store a nurse's schedule in a specific folder with the nurse’s name as the individual file’s name | | +| `*` | medical administrator | create a file with the nurse's name for later storage of the schedule | | +| `* * *` | medical administrator | store all the information of the patient and nurse in respective files | | +| `* * *` | medical administrator | delete a patient who no longer requires home visits | I do not need to include them in the scheduled exercise | +| `* * *` | medical administrator | delete the nurse that is no longer in this department | I would not schedule an unavailable nurse | +| `* *` | medical administrator | check how many patients are not yet scheduled for the current week ( /for a certain period) | I know whether I have finished scheduling | +| `* *` | medical administrator | check the list of unscheduled patients | schedule them now | +| `*` | medical administrator | check the list of nurses not going for a home visit on a specific date | if one of the nurses suddenly falls sick, I have to schedule her assigned patient with the other available nurse quickly | +| `*` | medical administrator | assess a patient's details by name and update their personal information or health condition | if there are any changes, edit them accordingly | +| `*` | medical administrator | assess a patient’s details by name and change their home visit’s date/ time | if the patient suddenly wants to change their appointment date/ time, also change it accordingly | +| `* * *` | medical administrator | mark a patient as scheduled | prevent scheduling a patient twice | +| `*` | medical administrator | also unmark a patient as unscheduled | if the patient changes the date for the home visits, I will remember to schedule the patient again by unmarking it | +| `* * *` | medical administrator | mark a nurse as fully scheduled | I will not match the nurse with the remaining patients since their home visit schedule is already full | +| `* *` | medical administrator | also unmark a nurse as not-fully scheduled | if one of their patients suddenly reschedules the dates, unmark the nurse and match the nurse with the remaining patients till it is full again | +| `*` | medical administrator | manage recurring home visits | I do not need to keep updating the date/time of the home visits | +| `*` | medical administrator | create a one-week schedule that contains the list of all the required patient details for a nurse | save it and export/send it to the nurse | +| `* *` | medical administrator | check whether there are time crashes in a nurse's schedule | if a time crash is detected, reschedule it again | +| `*` | medical administrator | check whether there are duplicate patients | the duplication can be detected and removed even if I accidentally add a patient into the system more than once | +| `*` | medical administrator | sort the list of patients by home visit date | I know which patient I need to schedule first | +| `* * *` | medical administrator | find patients by keywords/name (such as diabetic patient, Kent Ridge etc) | search the patients by keyword and assign them to the nurses | +| `*` | medical administrator | create location tags to label the patients | group them by labels and assign the groups to the nurses | +| `* *` | medical administrator | give patients different priorities | if a patient’s condition is more serious, I need to assign more nurses / more experienced nurses to the patient’s home visits | +| `*` | medical administrator | archive the patient records | there is still a record of the patient after deletion | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `Healthcare Xpress` and the **Actor** is the `medical administrator`, unless specified otherwise) + +**Use case: UC01 - List Patients / Nurses** + +**MSS** + +1. Medical administrator requests to list patients,nurses or both with or without specifications. +2. Healthcare Xpress shows a list of patients/nurses that satisfy the specifications. + + Use case ends. + +**Extensions** + +- 1a. The given inputs/specifications are invalid. + + - 1a1. Healthcare Xpress shows an error message. + + Use case ends. + +- 1b. There are no patients/nurses that satisfy the specifications. + + - 1b1. Healthcare Xpress shows a blank list. + + Use case ends. + +- \*a. At any time, medical administrator choose to exit the program. -**Use case: Delete a person** + Use case ends. + +**Use case: UC02 - Find a Specific Patient / Nurse** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Medical administrator requests to find a specific patient/nurse. +2. Healthcare Xpress shows that specific patient/nurse. + + Use case ends. + +**Extensions** + +- 1a. The given inputs are invalid. + + - 1a1. Healthcare Xpress shows an error message. + + Use case ends. + +- 1b. There is not only one patient/nurse that can match the find inputs. + + - 1b1. Healthcare Xpress returns a list of patients/nurses that matched and the first one being the most matched. Use case ends. +- \*a. At any time, medical administrator choose to exit the program. + + Use case ends. + +**Use case: UC03 - Delete a Patient / Nurse** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to delete a patient/nurse. +3. Healthcare Xpress deletes the patient/nurse. + + Use case ends. + **Extensions** -* 2a. The list is empty. +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- \*a. At any time, medical administrator chooses to exit the program. Use case ends. -* 3a. The given index is invalid. +**Use case: UC04 - Unmark a Patient's DateSlot** - * 3a1. AddressBook shows an error message. +**MSS** - Use case resumes at step 2. +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to unmark a specific patient's specific dateslot. +3. Healthcare Xpress unmarks the patient's specific dateslot as failed visit. -*{More to be added}* + Use case ends. -### Non-Functional Requirements +**Extensions** + +- 1a. Only nurse/nurses are shown. + + - 1a1. Medical administrator requests to unmark a nurse. + + - 1a2. Healthcare Xpress shows an error message. + + Use case ends. + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid number is not a patient. + + - 2b1. Healthcare Xpress shows an error message. -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. + Use case resumes at step 1. -*{More to be added}* +- 2c. The given index of the dateslot is out of bound of the dateslot list of the patient. + + - 2c1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- 2d. The given index gives a dateslot that has not pass. + + - 2d1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- \*a. At any time, medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC05 - Undo Unmark a Patient's DateSlot** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to undo unmark a specific patient's specific dateslot. +3. Healthcare Xpress undo unmarks the patient's specific dateslot as success visit. + + Use case ends. + +**Extensions** + +- 1a. Only nurse/nurses are shown. + + - 1a1. Medical administrator requests to undo unmark a nurse. + + - 1a2. Healthcare Xpress shows an error message. + + Use case ends. + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid number is not a patient. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The given index of the dateslot is out of bound of the dateslot list of the patient. + + - 2c1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- 2d. The given index gives a dateslot that has not pass. + + - 2d1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- 2e. The given index gives a dateslot that is in success visit status. + + - 2e1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- \*a. At any time, medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC06 - Edit a Patient / Nurse** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to edit a patient / nurse and provides the details to be edited. +3. Healthcare Xpress edits the specific details of the patient / nurse. + + Use case ends. + +**Extensions** + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given details to be edited is invalid / in wrong format. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- \*a. At any time, medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC07 - Add Home-Visit Date(s) and Slot(s) to a Patient** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to add date(s) and slot(s) to a patient. +3. Healthcare Xpress add the date(s) and slot(s) for home-visits to the patient. + + Use case ends. + +**Extensions** + +- 1a. Only nurse/nurses are shown. + + - 1a1. Medical administrator request to add date(s) and slot(s) to the nurse. + + - 1a2. Healthcare Xpress shows an error message. + + Use case ends. + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid number is not a patient. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The given date and slot is invalid or in wrong format. + + - 2c1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- \*a. At any time, medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC08 - Delete Home-Visit Date(s) and Slot(s) from a Patient** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to delete date(s) and slot(s) from a patient. +3. Healthcare Xpress deletes the date(s) and slot(s) for home-visits from the patient. + + Use case ends. + +**Extensions** + +- 1a. Only nurse/nurses are shown. + + - 1a1. Medical administrator request to delete a date and time from the nurse. + + - 1a2. Healthcare Xpress shows an error message. + + Use case ends. + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid number is not a patient. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The given index of the dateslot is out of bound of the dateslot list of the patient. + + - 2c1. Healthcare Xpress shows an error message. + + Use cases resumes at step 1. + +- 2d. The dateslot(s) given is/are assigned. + + - 2d1. Healthcare Xpress deletes the matching home-visits from the nurses. + + Use cases resumes at step 3. + +- \*a. At any time, medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC09 - Update Home-Visit Date(s) and Slot(s) for a Patient** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to update date and slot from a patient. +3. Healthcare Xpress updates the date and slot for home-visits from the patient. + + Use case ends. + +**Extensions** + +- 1a. Only nurse/nurses are shown. + + - 1a1. Medical administrator request to update a date and time from the nurse. + + - 1a2. Healthcare Xpress shows an error message. + + Use case ends. + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid number is not a patient. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The new date(s) and slot(s) given is/are invalid or in wrong format. + + - 2c1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2d. The given date and slot indexes are out of bounds of the date and slot list of the patient. + + - 2d1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2e. The given date and slot indexes are more than the dates and slots given. + + - 2e1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2f. The old date(s) and slot(s) to be updated is/are assigned. + + - 2f1. Healthcare Xpress deletes the matching home-visits from the nurses. + + Use case resumes at step 3. + +- \*a. At any time, medical administrator choose to exit the program. + + Use case ends. + +**Use case: UC10 - Assign a patient's dateslot(s) to the nurse** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to assign a patient's dateslot(s) to a nurse. +3. Healthcare Xpress assigns the patient's dateslot(s) to the nurse. + + Use case ends. + +**Extensions** + +- 2a. Any given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given uid numbers are both patients or nurses. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The given uid number's nurse has another home-visit at the same date and time. + + - 2c1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2d. The given uid number's nurse are unavailable on that date. + + - 2d1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2e. The given index of the date slot is out of bound of the date slot list of the patient. + + - 2e1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2f. The given index of the date slot gives a date slot that has pass. + + - 2f1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- \*a. At any time, Medical administrator chooses to exit the program. + + Use case ends. + +**Use case: UC11 - Deassign patient's dateslot(s) / nurse's homevisit(s)** + +**MSS** + +1. Medical administrator requests to list patients / nurses (UC01) or find a specific patient / nurse (UC02). +2. Medical administrator requests to deassign patient's dateslot(s) / deassign nurse'homevisit(s). +3. Healthcare Xpress deassigns the patient's dateslot(s) from the respective nurse / deassigns the nurse's homevisit(s). + + Use case ends. + +**Extensions** + +- 2a. The given uid number is invalid. + + - 2a1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2b. The given index of the date slot is out of bound of the date slot list of the patient. + + - 2b1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2c. The given index of the date slot gives a date slot that has pass. + + - 2c1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- 2d. The given index of the date slot gives a date slot that has not been assigned. + + - 2d1. Healthcare Xpress shows an error message. + + Use case resumes at step 1. + +- \*a. At any time, medical administrator choose to exit the program. + + Use case ends. + +### Non-Functional Requirements + +1. Technical Requirements: + 1. The application should work on any _mainstream OS_, such as Windows, Linux, and macOS, as long as it has Java `11` or above installed. + 2. The application should be compatible with both _32-bit_ and _64-bit_ environments. +2. Performance Requirements: + 1. Should be able to hold up to _10000 patients and nurses_ without noticeable sluggishness in performance for typical usage. + 2. The application should be able to launch within _5 seconds_. + 3. The application should be able to respond to each command within _1 second_. +3. Quality requirements: + 1. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) + should be able to accomplish most of the tasks faster using commands than using the mouse. + 2. A user with minimal knowledge on how to operate text-based applications should be able to quickly learn how to use it. +4. Constraints: + 1. Each version of the application should be _backwards compatible_ with data produced by earlier versions. + 2. Specifications of dates and times should be compliant with ISO 8601 standard, and in the GMT+8 time zone. +5. Project Scope: + 1. The application is not required to handle the printing of the patient-nurse visitation schedule. ### Glossary +* **Medical Administrator**: A person who oversees, plans, directs, and coordinates home-visits for patients. +* **Patients**: A person receiving or registered to receive home visits due to special needs. +* **Nurses**: A person trained to care for the sick or infirm, especially trained to do home-visiting. +* **Healthcare Xpress**: A desktop app for managing patients that require home-visits. * **Mainstream OS**: Windows, Linux, Unix, OS-X * **Private contact detail**: A contact detail that is not meant to be shared with others --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Instructions for manual testing** @@ -347,7 +1041,7 @@ testers are expected to do more *exploratory* testing. 1. Resize the window to an optimum size. Move the window to a different location. Close the window. 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + Expected: The most recent window size and location is retained. 1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..3c5928e28ab 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,40 +3,42 @@ 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. +Healthcare Xpress is a **desktop app for managing patients that require home-visits and nurses, 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, Healthcare Xpress can get your contact management tasks done faster than traditional GUI apps. -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## Quick start 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +1. Download the latest [healthcarexpress.jar](https://github.com/AY2223S1-CS2103-F13-4/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +1. Copy the file to the folder you want to use as the _home folder_ for your Healthcare Xpress Record System. 1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) + ![Ui](images/UiV1.3.png) 1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: - * **`list`** : Lists all contacts. + - **`list`** : Lists all contacts. + + - **`add`** `c/N n/Jane p/98723432 e/jason@example.com g/F t/Asthma` : Adds a nurse named `Jane` to the Healthcare Xpress book. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + - **`add`** `c/P n/John p/98765432 e/john@example.com g/M a/Bishan street, block 123, #01-01 t/Asthma ds/2022-12-12,3` : Adds a patient named **`John`** to Healthcare Xpress book. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + - **`assign`** `id/1 id/3 dsi/1` : Assigns the first dateslot of the patient with id of 3 to the nurse with id of 1. - * **`clear`** : Deletes all contacts. + - **`delete`** `id/3` : Deletes the nurse of patient with an id of 3. - * **`exit`** : Exits the app. + - **`clear`** : Deletes all contacts. + + - **`exit`** : Exits the app. 1. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- +--- ## Features @@ -44,103 +46,332 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo **:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+- 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 including zero times.
+ e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order.
+- Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
+- If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+- Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-### Viewing help : `help` +
+ +**:information_source: Additional information:**
+ +- HomeVisits are the visits to the patient's home that the nurse has to attend to. They are displayed in the format `Date and Time : [UID] Patient Uid`. They cannot be added using the add command. Instead, they can only be added using the assign command. -Shows a message explaning how to access the help page. +- HomeVisit DateSlots refer to home visit date slots of the patient. They are displayed in the format `[ ][ ] Date and Time`. -![help message](images/helpMessage.png) + - The first bracket indicates if this date slot has been assigned to a nurse. (Blank `[ ]` - not assigned, `[A]` - assigned). + + - The second bracket indicates if this date slot has passed and visited. (Blank `[ ]` - the date slot has not passed, `[V]` - the date slot has passed and been automatically marked as visited, `[FV]` - the date slot has passed but the nurse failed to visit). + +
+ +### Viewing help : `help` + +Opens the help dialog, where help info for each command can be viewed. Format: `help` +![help message](images/Help.png) + +:bulb:**Tips:**
-### Adding a person: `add` +- You may look at the bottom of the miniature Healthcare Xpress to see summary of code use. -Adds a person to the address book. +![help message](images/HelpQuickCommandHelp.png) -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +### Adding a nurse or patient: `add` + +1. Adds a patient to the Healthcare Xpress Record System. + +Format: `add c/P n/NAME p/PHONE_NUMBER e/EMAIL g/GENDER a/ADDRESS [t/TAG]…​ [ds/DATE_AND_SLOT]…​` + +
+ +:bulb:**Tips:**
+ +- A patient can have any number of tags (including 0). + +- A patient can have any number of home-visit date and slot (including 0). + +- Date and slot need to be in the format of `yyyy-MM-dd,SLOT_NUMBER`, eg `2022-11-11,2`. + +- The slot timing is fixed and slots are only from 10am to 4pm. + +- **Slot 1: 10am, Slot 2: 12pm, Slot 3: 2pm, Slot 4: 4pm.** The `SLOT_NUMBER` can only be from 1 to 4. + +- To add a patient, type `c/P` specifically. -
:bulb: **Tip:** -A person can have any number of tags (including 0)
Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` -### Listing all persons : `list` +- `add c/P n/John p/98765432 e/john@example.com g/M a/Bishan street, block 123, #01-01 t/asthma ds/2022-12-12,4` +- `add c/P n/Jackson g/M t/heartDisease a/Bishan Street 32, Singapore 291038 #04-11 p/9019390 e/jackson@gmail.com ds/2022-11-11,3 ds/2022-12-12,3` + +![add patient](images/AddPatient.png) + + +2. Adds a nurse to the Healthcare Xpress Record System + +Format: `add c/N n/NAME p/PHONE_NUMBER e/EMAIL g/GENDER a/ADDRESS [t/TAG]…​ [ud/UNAVAILABLE_DATE]…​` -Shows a list of all persons in the address book. +
-Format: `list` +:bulb:**Tips:**
+ +- A nurse can have any number of tags (including 0). + +- A nurse can have any number of unavailable dates (including 0). + +- Unavailable dates must be in `yyyy-MM-dd` format, eg `2022-11-11`. + +- You may type it in any order. + +- To add a nurse, type `c/N` specifically. + +
+ +Examples: + +- `add c/N n/Jason p/98723432 e/jason@example.com g/M a/Blk 855 Woodlands Street 83, Singapore 730855, block 123, #01-01 t/asthma ud/2022-11-11` +- `add c/N n/Lily g/F p/92091883 e/lily@gmail.com ud/2022-11-11 a/Woodland Street 21 Block 211 #01-02 t/heartDiseaseSpecialist` + +![add nurse](images/AddNurse.png) + +### Listing nurses or patients : `list` + +Shows a list of specified nurses or patients, or all nurses and patients if no specifications were provided. + +Format: `list [c/CATEGORY] [t/TAG] [g/GENDER] [a/ADDRESS]` + +ADDRESS: Non-exact address matching (e.g. Searching for `an` returns `Ang Mo Kio`, `Woodlands`) + +CATEGORY: Only accepts `N` for NURSES or `P` for PATIENTS. (case-insensitive) + +GENDER: Only accepts `M` for MALE or `F` for FEMALE. (case-insensitive) + +TAG: Exact, case-sensitive tag matching (e.g. Searching for `dia` does not return `DIABETIC`) + +Examples: + +- `list c/P t/DIABETIC g/M` - Lists all the male diabetic patients enrolled in the database. +- `list c/P a/Bugis t/heartDisease` - Lists all the patients tagged with heart disease in the Bugis region. +- `list c/N` - Lists all the nurses enrolled in the database. + +![list nurse](images/ListNurse.png) ### Editing a person : `edit` -Edits an existing person in the address book. +Edits an existing person in the Healthcare Xpress Record System. + +Format: `edit id/ID [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​ [ds/DATE_AND_SLOT]…​ [dsi/DATE_AND_SLOT_INDEX]…​ [ud/UNAVAILABLE_DATE]…​ [udi/UNAVAILABLE_DATE]…​` + +- Edits the person with the specified `ID`. +- The ID refers to the unique ID number shown in the displayed person list. +- The ID **must be a positive integer** 1, 2, 3, …​ +- At least one of the optional fields must be provided. +- Existing values will be updated to the input values. +- When **editing tags**, the **existing tags of the person will be removed** i.e adding of tags is not cumulative. +- You can **remove all the person’s tags** by typing `t/` without specifying any tags after it. +- The `DATE_AND_SLOT_INDEX` is the index of the date and slot list of a patient starting from 1. +- When **editing date and slot**, there are **4 options**: + 1. To delete all the existing date and slot of a patient: you can type `ds/` or `dsi/` or `ds/ dsi/` without specifying a date and slot or its index after it. + 2. To delete a specific date and slot of a patient: you can type `ds/ dsi/TO_BE_DELETED_DATE_AND_TIME_INDEX` or `dsi/TO_BE_DELETED_DATE_AND_TIME_INDEX`. The to be deleted date and slot index is the index of the specific date and slot you want to delete. + 3. To add a new date and slot of a patient: you can type `ds/NEW_DATE_AND_SLOT dsi/` or `ds/NEW_DATE_AND_SLOT`. + 4. To change a specific date and slot of a patient: you can type `ds/UPDATE_DATE_AND_SLOT dsi/TO_BE_UPDATED_DATE_AND_SLOT_INDEX`. The date and slot at this index in the list will be updated to the new date and slot given by you. +- You can only use `ds/` and `dsi/` for **patients**. Nurses do not have any home-visit dates and slots. +- The unavailable date works similar to the date and time edit, only using different indicators `ud/` and `udi/` to indicate the date and the index. +- The **unavailable date** is only applicable to **nurses**. + +
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +:exclamation: **Caution:**
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +- When changing a date slot in the existing list, if the old date slot is assigned, it will be deassigned and the new date slot will be in "not assigned status". + +- When changing an unavailable date in the existing list or adding a new unavailable date in the existing list from a nurse, the date slot assigned to that nurse will be checked against the unavailable date. If the date slot is on the same day with the unavailable date, it will auto deassign that date slot from the nurse. + +
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. -### Locating persons by name: `find` +- `edit id/1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the nurse/patient with id 1 to be `91234567` and `johndoe@example.com` respectively. +- `edit id/2 n/Betsy Crower t/` Edits the name of the nurse/patient with id 2 to be `Betsy Crower` and clears all existing tags. +- `edit id/2 dsi/1 ds/2022-12-11,1` Change the first date and time of the patient with id 2 to `2022-12-11,1`. +- `edit id/1 e/bsy@gmail.com ud/2022-12-10 p/9029901` Change the email to `bsy@gmail.com` and phone to `9029901` and add unavailable date `2022-12-10` to the nurse with id 1. -Finds persons whose names contain any of the given keywords. +![edit](images/Edit.png) + +### Locating patients or nurses by name: `find` + +Finds patients or nurses in the database 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). +- 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` Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +- `find John` returns `john` and `John Doe` +- `find alex david` returns `Alex Yeoh`, `David Li`
+- `find Jason` returns `Jason` + +![find](images/Find.png) + +### Deleting a nurse or a patient : `delete` -Deletes the specified person from the address book. +Deletes the specified nurse or patient from the record system. -Format: `delete INDEX` +Format: `delete id/ID` -* 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, …​ +- Deletes the nurse or patient with the specified `ID`. +- The ID refers to the unique ID number shown in the displayed person list. +- The ID **must be a positive integer** 1, 2, 3, …​ Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. + +- `list` followed by `delete id/2` deletes the nurse/patient with an id of 2. +- `delete id/10` deletes the nurse/patient with an id of 10. + +![delete](images/Delete.png) + +### Assigning a patient's homevisit date slot to a nurse : `assign` + +Assigns a specific patient's date slot(s) to a nurse. + +Format `assign id/NURSE'S_ID id/PATIENT'S_ID [dsi/DATE_AND_SLOT_INDEX]…​` + +- Assigns the date slots of the patient with the specified 'PATIENT'S_ID' to the nurse with the specified 'NURSE'S_ID'. +- The ID refers to the unique ID shown in the displayed person list. +- The ID **must be a positive integer** 1, 2, 3, ... +- There **must be 2 (and only 2) IDs, 1 belonging to a patient and 1 belonging to a nurse**. +- There is no specific order for the 2 IDs. (i.e. The patient's id or the nurse's id can come first, as long as one belongs to a patient and the other belongs to a nurse.) +- The assign command can have any number of date and slot index (including 0). +- If the **'DATE_AND_SLOT_INDEX' is not indicated** (0), then all the date slot of the patients will be assigned to the nurse. +- If the **'DATE_AND_SLOT_INDEX(ES)' is indicated**, then the date slot with the respective index(es) in the displayed dateslot list will be assigned to the nurse. +- When assigning, it will check whether there are **time clashes** and ensures that the nurse is not **unavailable** on that day. + +Examples: + +- `assign id/1 id/2` assign all the date slots of patient with id of 2 to nurse with id of 1. +- `assign id/2 id/1 dsi/2` assign the date slot with index 2 in the dateslot list of patient with id of 2 to nurse with id of 1. +- `assign id/1 id/7 dsi/3` assign the third date slot in the dateslot list of patient with id 7 to nurse with id 1. + +![assign](images/Assign.png) + +### Deassigning a patient's homevisit date slot from a nurse : `deassign` + +Deassigns a specific patient's date slot from a nurse. + +Format `deassign id/ID [dsi/DATE_AND_SLOT_INDEX]…​` + +- The 'ID' can belong to either a patient or a nurse. +- The deassign command can have any number of date and slot index (including 0). +- If it is a **patient id and no date and slot index is** indicated, it will deassign all the date slots of the patient from the respective nurse. +- If it is a **patient id and date and slot index is** indicated, it will deassign the specific date slots (with the date and slot index indicated) of the patient from the respective nurse. +- If it is a **nurse id and no date and slot index is** indicated, it will deassign **all** the homevisits of the nurse and the respective patient's date slot will change to `not assigned` status. +- If it is a **nurse id and date and slot index is** indicated, it will deassign the specified homevisits of the nurse (with the date and slot index indicated) and the respective patient's date slot will change to `not assigned` status. + +Examples: + +- `deassign id/1` deassigns all the date slots of nurse with id of 1. +- `deassign id/2 dsi/2` deassigns the date slot with index 2 in the dateslot list of patient with id of 2. +- `deassign id/1 dsi/2` deasigns the second home visits in the homevisit list of nurse with id of 1. + +![deassign](images/Deassign.png) + +### Unmarking a patient's dateslot : `unmark` + +Unmarks a specific patient's specific dateslot in the records system, indicating a failed visit. + +Format: `unmark id/PATIENT_ID dsi/DATE_AND_SLOT_INDEX` + +- Unmarks the patient with the specified 'ID' as having a failed visit. +- The ID refers to the unique ID shown in the displayed person list. +- The ID **must be a positive integer** 1, 2, 3, ... +- The ID **must belong to a patient**. +- The **DATE_AND_SLOT_INDEX must be indicated** and only **1** can be indicated. +- The **date that is to be unmarked must be a date that has passed**. eg, if today is 2022-11-11, the date to be unmarked must be a date before 2022-11-11. + +Examples: + +- `unmark id/7 dsi/1` unmarks the dateslots of index 1 in the dateslot list of the patient with id of 7. + +![unmark](images/Unmark.png) + +### Undo unmarking a patient as visited : `undounmark` + +Undoes the unmarking of a specific patient's specific dateslot in the records system, indicating that the visit was successful. + +Format: `undounmark id/ID dsi/DATE_SLOT_INDEX` + +- Undo unmark of the specific dateslot with specified 'DATE_SLOT_INDEX' of a patient with the specified 'ID' as having been visited. +- This feature is for user to undo the unmark fail visit date slot so that if a patient was marked as failed visit by accident, this command can be used to undo that and the dateslot's status will be reverted to `visited`. +- The ID refers to the unique ID shown in the displayed person list. +- The ID **must be a positive integer** 1, 2, 3, ... +- `list` or `find` operations can be performed first to get the ID of the desired patient. +- If the 'Date_Slot_Index' refers to a dateslot that has not passed, or its status is already `visited`, **the command will be ignored**. + +Examples: + +- `undounmark id/7 dsi/1` undo unmarks the first dateslot of the patient with id of 7 as having been visited. + +![undounmark](images/UndoUnmark.png) + +### Check for similar persons : `checkSimilar` + +Checks and returns a list of similar persons so that the user can check if any errors has been made in entries + +Format: `checkSimilar` + +> Tip: `checkSimilar` checks for `name`, `phone`, `email`, `gender`, `tags`, `address` and returns both people if +> they match 5 out of 6 conditions. + +![checksimilar](images/checkSimilar.png) + +### Updating a patient's emergency contact information: `updatecontact` + +Updates a patient's contact information for next-of-kin or attending physician. + +Format: `updatecontact id/PATIENT'S_ID c/CATEGORY n/CONTACT_NAME p/CONTACT_PHONE e/CONTACT_EMAIL` + +- PATIENT'S_ID: Unique ID of the patient whose emergency contact is to be updated. +- CATEGORY: Only accepts `K` for next-of-kin or `D` for attending physician. +- CONTACT_NAME: Name of the emergency contact. +- CONTACT_PHONE: Phone number of the emergency contact. +- CONTACT_EMAIL: Email address of the emergency contact. +- Note: The UID must belong to a **patient**. +- Note: Only `K` or `D` are accepted for category. +- Note: This command replaces the current next-of-kin or attending physician contact info, if there is already an existing one. + +Examples: + +- `updatecontact id/3 c/K n/John Doe p/81234567 e/johndoe@example.com` + - updates NoK contact information for patient UID 3. +- `updatecontact id/3 c/D n/kw p/9013890 e/kw@gmail.com` + - updates attending physician contact information for patient UID 3. + +![updatecontact](images/updatecontact.png) ### Clearing all entries : `clear` @@ -156,37 +387,50 @@ Format: `exit` ### 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. +Healthcare Xpress data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +Healthcare Xpress data is saved as a JSON file `[JAR file location]/data/healthcarexpress.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, Healthcare Xpress Record System will discard all data and start with an empty data file at the next run. + +- NOT RECOMMENDED : It is not recommended to change the 'date slot' of a patient and 'homevisits', 'unavailable date' and 'fully scheduled date' of a nurse in the file. The system is unable to check the correctness and whether there is time clash and other issues if you change it manually in the 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.
### Archiving data files `[coming in v2.0]` _Details coming soon ..._ --------------------------------------------------------------------------------------------------------------------- +--- ## FAQ **Q**: How do I transfer my data to another Computer?
**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. --------------------------------------------------------------------------------------------------------------------- +--- ## 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 (Nurse)** | `add c/N n/NAME p/PHONE_NUMBER e/EMAIL g/GENDER a/ADDRESS [t/TAG]…​ [ud/UNAVAILABLE_DATE]…​ `
e.g., `add c/N n/Jason p/98723432 e/jason@example.com g/M t/asthma a/Yishun Street 211, block 230, #03-03 ud/2022-11-11` | +| **Add (Patient)** | `add c/P n/NAME p/PHONE_NUMBER e/EMAIL g/GENDER a/ADDRESS [ds/DATE_AND_SLOT]…​ [t/TAG]…​`
e.g., `add c/P n/John p/98765432 e/john@example.com g/M a/Bishan street, block 123, #01-01 t/Asthma ds/2022-12-12,2` | +| **Assign** | `assign id/PATIENT_ID id/NURSE_ID [dsi/DATE_SLOT_INDEX]…​`
e.g., `assign id/1 id/2 dsi/2` | +| **Clear** | `clear` | +| **Deassign** | `deassign id/ID [dsi/DATE_SLOT_INDEX]…​`
e.g., `deassign id/1 dsi/2` | +| **Delete** | `delete id/ID`
e.g., `delete id/3` | +| **Edit** | `edit id/ID [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​ [ds/DATE_SLOT]…​ [dsi/DATE_SLOT_INDEX]…​ [ud/UNAVAILABLE_DATE]…​ [udi/UNAVAILABLE_DATE_INDEX]…​`
e.g.,`edit id/2 n/James Lee e/jameslee@example.com` | +| **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | +| **Help** | `help` | +| **List** | `list [c/CATEGORY] [t/TAG] [g/GENDER] [a/ADDRESS]`
e.g., `list c/n` | | +| **Unmark** | `unmark id/PATIENT_ID dsi/DATE_SLOT_INDEX`
e.g., `unmark id/1 dsi/1` | +| **UndoUnmark** | `undounmark id/PATIENT_ID dsi/DATE_SLOT_INDEX`
e.g., `undounmark id/1 dsi/1` | +| **CheckSimilar** | `checkSimilar` | +| **UpdateContact** | `updatecontact id/PATIENT_ID c/CATEGORY n/CONTACT_NAME p/CONTACT_PHONE e/CONTACT_EMAIL`
e.g., `updatecontact id/3 c/D n/Farihah p/2901939 e/hah@gmail.com` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..1eb4ff5832a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Healthcare Xpress" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103-F13-4/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..bee772a5a94 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: "Healthcare Xpress"; font-size: 32px; } } diff --git a/docs/diagrams/AddPatientActivityDiagram.puml b/docs/diagrams/AddPatientActivityDiagram.puml new file mode 100644 index 00000000000..ed6152e40d9 --- /dev/null +++ b/docs/diagrams/AddPatientActivityDiagram.puml @@ -0,0 +1,20 @@ +@startuml +start +:User executes add patient command; + +if () then ([all the compulsory fields are present and valid]) + + if () then ([there is potential duplication]) + : Show error message about the potential duplication; + else ([else]) + :Add Patient into addressBook; + :Save the updated addressBook; + endif +else ([else]) +:Show error message about the missing or invalid fields; + +endif +stop +@enduml + + diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml new file mode 100644 index 00000000000..0e29739b44e --- /dev/null +++ b/docs/diagrams/AddSequenceDiagram.puml @@ -0,0 +1,86 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant "a:AddCommand" as AddCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "uid:Uid" as Uid MODEL_COLOR +participant "p:Patient" as Patient MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("add userInput") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("add userInput") +activate HealthcareXpressParser + +create AddCommandParser +HealthcareXpressParser -> AddCommandParser +activate AddCommandParser + +AddCommandParser --> HealthcareXpressParser +deactivate AddCommandParser + +HealthcareXpressParser -> AddCommandParser : parse("userInput") +activate AddCommandParser + +create Uid +AddCommandParser -> Uid +activate Uid + +Uid -> AddCommandParser : uid +deactivate Uid + +create Patient +AddCommandParser -> Patient : uid, patientDetails +activate Patient + +Patient -> AddCommandParser : p +deactivate Patient + +create AddCommand +AddCommandParser -> AddCommand : p +activate AddCommand + +AddCommand --> AddCommandParser : a +deactivate AddCommand + +AddCommandParser --> HealthcareXpressParser : a +deactivate AddCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCommandParser -[hidden]-> HealthcareXpressParser +destroy AddCommandParser + +HealthcareXpressParser --> LogicManager : a +deactivate HealthcareXpressParser + +LogicManager -> AddCommand : execute() +activate AddCommand + +AddCommand -> Model : addPerson(p) +activate Model + +Model --> AddCommand +deactivate Model + +create CommandResult +AddCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCommand +deactivate CommandResult + +AddCommand --> LogicManager : result +deactivate AddCommand + +[<--LogicManager +deactivate LogicManager +@enduml + diff --git a/docs/diagrams/AppointmentClassDiagram.puml b/docs/diagrams/AppointmentClassDiagram.puml new file mode 100644 index 00000000000..dd778d49371 --- /dev/null +++ b/docs/diagrams/AppointmentClassDiagram.puml @@ -0,0 +1,19 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <> { + class Patient + Class Nurse + Class Appointment + Patient "1" - "0..1" Nurse + (Patient, Nurse) .. Appointment + Class Slot + Class VisitStatus + Appointment --> Slot + Appointment --> VisitStatus +} + +@enduml diff --git a/docs/diagrams/AssignActivityDiagram.puml b/docs/diagrams/AssignActivityDiagram.puml new file mode 100644 index 00000000000..b0d036216e9 --- /dev/null +++ b/docs/diagrams/AssignActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +start +:User executes assign command; + +if () then ([all the compulsory fields are present and valid]) + + if () then ([both persons are nurses or patients]) + : Show error message about both persons being nurses or both persons being patients; + else ([else]) + :Edit Patient dateslots; + :Edit Nurses homevists; + :Show all persons in the displayed list; + endif +else ([else]) +:Show error message about the missing or invalid fields; + +endif +stop +@enduml diff --git a/docs/diagrams/AssignSequenceDiagram.puml b/docs/diagrams/AssignSequenceDiagram.puml new file mode 100644 index 00000000000..2445aa0e8af --- /dev/null +++ b/docs/diagrams/AssignSequenceDiagram.puml @@ -0,0 +1,100 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR +participant ":AssignCommandParser" as AssignCommandParser LOGIC_COLOR +participant "a:AssignCommand" as AssignCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant ":InternalEditor" as InternalEditor LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("assign userInput") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("assign userInput") +activate HealthcareXpressParser + +create AssignCommandParser +HealthcareXpressParser -> AssignCommandParser +activate AssignCommandParser + +AssignCommandParser --> HealthcareXpressParser +deactivate AssignCommandParser + +HealthcareXpressParser -> AssignCommandParser : parse("userInput") +activate AssignCommandParser + +create AssignCommand +AssignCommandParser -> AssignCommand : uid1, uid2, indexList +activate AssignCommand + +AssignCommand --> AssignCommandParser : +deactivate AssignCommand + +AssignCommandParser --> HealthcareXpressParser : +deactivate AssignCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AssignCommandParser -[hidden]-> HealthcareXpressParser +destroy AssignCommandParser + +HealthcareXpressParser --> LogicManager : +deactivate HealthcareXpressParser + +LogicManager -> AssignCommand : execute() +activate AssignCommand + +create InternalEditor +AssignCommand -> InternalEditor +activate InternalEditor +InternalEditor -> AssignCommand +deactivate InternalEditor + +AssignCommand -> InternalEditor: editPatient(p,l) +activate InternalEditor +InternalEditor -> Model : setPerson(p1, p2) +activate Model +Model -> InternalEditor +deactivate Model + +InternalEditor -> Model : updateFilteredPersonList(predicate) +activate Model +Model -> InternalEditor +deactivate Model + +deactivate InternalEditor + +AssignCommand -> InternalEditor: editNurse(p,l) +activate InternalEditor +InternalEditor -> Model : setPerson(p1, p2) +activate Model +Model -> InternalEditor +deactivate Model + +InternalEditor -> Model : updateFilteredPersonList(predicate) +activate Model +Model -> InternalEditor +deactivate Model + +InternalEditor -> AssignCommand +deactivate InternalEditor + +create CommandResult +AssignCommand -> CommandResult +activate CommandResult + +CommandResult --> AssignCommand +deactivate CommandResult + +AssignCommand --> LogicManager : result +deactivate AssignCommand + +[<--LogicManager +deactivate LogicManager +@enduml + diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 6a6b23a006f..2d3b4d3c966 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -5,9 +5,9 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits HealthcareXpress]) :Purge redundant states; - :Save AddressBook to + :Save HealthcareXpress to addressBookStateList; else ([else]) endif diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..2d7b44bb87b 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,7 +3,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR @@ -16,17 +16,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> HealthcareXpressParser : parseCommand("delete 1") +activate HealthcareXpressParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +HealthcareXpressParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> HealthcareXpressParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +HealthcareXpressParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -36,14 +36,14 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> HealthcareXpressParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> HealthcareXpressParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +HealthcareXpressParser --> LogicManager : d +deactivate HealthcareXpressParser LogicManager -> DeleteCommand : execute() activate DeleteCommand diff --git a/docs/diagrams/ListActivityDiagram.puml b/docs/diagrams/ListActivityDiagram.puml new file mode 100644 index 00000000000..6136e89763c --- /dev/null +++ b/docs/diagrams/ListActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start +:User executes list command; + +if () then ([no criteria given]) + if () then ([one or more invalid arguments]) + :Show error message about invalid arguments + :Filter list of patients and nurses based on given specifications; + :Display filtered list of patients and nurses; + else ([else]) + endif +else ([else]) +:display all patients and nurses; +endif +stop +@enduml + + diff --git a/docs/diagrams/ListSequenceDiagram.puml b/docs/diagrams/ListSequenceDiagram.puml new file mode 100644 index 00000000000..a0407063dfd --- /dev/null +++ b/docs/diagrams/ListSequenceDiagram.puml @@ -0,0 +1,48 @@ +@startuml + +participant ":LogicManager" as LogicManager +participant ":HealthcareXpressParser" as HealthcareXpressParser +participant ":ListCommandParser" as ListCommandParser +participant ":ListCommand" as ListCommand +participant ":CommandResult" as CommandResult + +participant ":Model" as Model + +[-> LogicManager : execute("list c/n g/f") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("list c/n g/f") +activate HealthcareXpressParser + +create ListCommandParser +HealthcareXpressParser -> ListCommandParser +activate ListCommandParser + +ListCommandParser --> HealthcareXpressParser + +HealthcareXpressParser -> ListCommandParser : parse("list c/n g/f") + +create ListCommand +ListCommandParser --> ListCommand +activate ListCommand +ListCommand --> ListCommandParser +ListCommandParser --> HealthcareXpressParser : ListCommand +deactivate ListCommandParser +HealthcareXpressParser --> LogicManager : ListCommand +deactivate HealthcareXpressParser + +LogicManager --> ListCommand : execute() +ListCommand --> Model: filter(predicate) +activate Model +Model --> ListCommand +deactivate Model + +create CommandResult +ListCommand --> CommandResult +CommandResult --> ListCommand +ListCommand --> LogicManager : CommandResult +deactivate ListCommand +<- LogicManager : CommandResult + +@enduml + diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..7f4ed78b36d 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR package Logic { -Class AddressBookParser +Class HealthcareXpressParser Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command @@ -27,8 +27,8 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right->"1" HealthcareXpressParser +HealthcareXpressParser ..> XYZCommand : creates > XYZCommand -up-|> Command LogicManager .left.> Command : executes > diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..fa03246e764 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -14,12 +14,11 @@ Class UserPrefs Class UniquePersonList Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag - +Class BasePerson +Class Physician +Class NextOfKin +Class Patient +Class Nurse } Class HiddenOutside #FFFFFF @@ -36,15 +35,12 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email + +Person .up.|> BasePerson +Physician .up.|> BasePerson +NextOfKin .up.|> BasePerson +Patient .up.|> Person +Nurse .up.|> Person ModelManager -->"~* filtered" Person @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 0c7424de6e0..ad15bf475e8 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Class "<>\nParser" as Parser -Class AddressBookParser +Class HealthcareXpressParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> HealthcareXpressParser -AddressBookParser .down.> XYZCommandParser: creates > +HealthcareXpressParser .down.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > +HealthcareXpressParser ..> Command : returns > XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/PatientClassDiagram.puml b/docs/diagrams/PatientClassDiagram.puml new file mode 100644 index 00000000000..4ef89546adf --- /dev/null +++ b/docs/diagrams/PatientClassDiagram.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <> { +Class BasePerson +Class Physician +Class NextOfKin +Class Person +Class Address +Class DateSlot +Class Gender +Class Email +Class Name +Class Patient +Class Phone +Class Tag +Class Uid +} + +Physician .up.|> BasePerson +NextOfKin .up.|> BasePerson +Person .up.|> BasePerson +BasePerson *--> "1" Name +BasePerson *--> "1" Phone +BasePerson *--> "1" Email + +Person *--> "1" Uid +Person *--> "1" Gender +Person *--> "1" Address +Person *--> "*" Tag +Patient .up.|> Person +Patient *--> "*" DateSlot +Patient *--> "0..1" Physician +Patient *--> "0..1" NextOfKin + +Uid -[hidden]right-> Name +Name -[hidden]right-> Gender +Gender -[hidden]right-> Phone +Phone -[hidden]right-> Address +Address -[hidden]right-> Email + +@enduml + diff --git a/docs/diagrams/PersonClassDiagram.puml b/docs/diagrams/PersonClassDiagram.puml new file mode 100644 index 00000000000..8bc502e7ebf --- /dev/null +++ b/docs/diagrams/PersonClassDiagram.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ +Class BasePerson +Class Person +Class Patient +Class Nurse +Class NextOfKin +Class Physician +Class Address +Class Email +Class Name +Class Phone +Class Tag +Class DateSlot +Class HomeVisit +} + +BasePerson *--> Name +BasePerson *--> Phone +BasePerson *--> Email + +Person .up.|> BasePerson +Person *--> Address +Person *--> "*" Tag + +Physician .up.|> BasePerson +NextOfKin .up.|> BasePerson + +Patient .up.|> Person +Patient *--> "*" DateSlot +Patient *--> "0..1" Physician +Patient *--> "0..1" NextOfKin + +Nurse .up.|> Person +Nurse *--> "*" HomeVisit + +Physician .[hidden]right.> Person +NextOfKin .[hidden]right.> Physician + +@enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..96fe405a86d 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -6,15 +6,15 @@ skinparam ClassBorderColor #000000 title Initial state package States { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..2142cb550d6 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "delete 5" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..8edc586d4f9 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -6,15 +6,15 @@ skinparam ClassBorderColor #000000 title After command "add n/David" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..8c6be202c85 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -6,15 +6,15 @@ skinparam ClassBorderColor #000000 title After command "undo" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..142443b5a4b 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -6,15 +6,15 @@ skinparam ClassBorderColor #000000 title After command "list" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..5d9c891c1bc 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -6,16 +6,16 @@ skinparam ClassBorderColor #000000 title After command "clear" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab3:AddressBook__" + class State1 as "__hx0:HealthcareXpress__" + class State2 as "__hx1:HealthcareXpress__" + class State3 as "__hx2:HealthcareXpress__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 -note right on link: State ab2 deleted. +note right on link: State hx2 deleted. @end diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..63f7f65d73c 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -3,7 +3,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box @@ -14,18 +14,18 @@ end box [-> LogicManager : execute(undo) activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> HealthcareXpressParser : parseCommand(undo) +activate HealthcareXpressParser create UndoCommand -AddressBookParser -> UndoCommand +HealthcareXpressParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> HealthcareXpressParser deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +HealthcareXpressParser --> LogicManager : u +deactivate HealthcareXpressParser LogicManager -> UndoCommand : execute() activate UndoCommand diff --git a/docs/diagrams/UndoUnmarkActivityDiagram.puml b/docs/diagrams/UndoUnmarkActivityDiagram.puml new file mode 100644 index 00000000000..10790cd44ac --- /dev/null +++ b/docs/diagrams/UndoUnmarkActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml + +start +:User executes mark command; + +if () then ([uid and dateslot index are both present]) + if () then ([uid refers to a valid patient]) + if () then ([dateslot index is valid]) + if () then ([dateslot at index is unmarked]) + :Mark the dateslot at the index for the patient with uid; + else ([else]) + :Show error message about dateslot already being marked; + endif + else ([else]) + :Show error message about dateslot index being invalid; + endif + else ([else]) + :Show error message about invalid uid; + endif +else ([else]) + :Show error message about missing uid and/or missing dateslot index; +endif +stop +@enduml diff --git a/docs/diagrams/UndoUnmarkSequenceDiagram.puml b/docs/diagrams/UndoUnmarkSequenceDiagram.puml new file mode 100644 index 00000000000..896b3827584 --- /dev/null +++ b/docs/diagrams/UndoUnmarkSequenceDiagram.puml @@ -0,0 +1,84 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR +participant ":UndoUnmarkCommandParser" as UndoUnmarkCommandParser LOGIC_COLOR +participant "m:UndoUnmarkCommand" as UndoUnmarkCommand LOGIC_COLOR +participant ":InternalEditor" as InternalEditor LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("unmark userInput") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("unmark userInput") +activate HealthcareXpressParser + +create UndoUnmarkCommandParser +HealthcareXpressParser -> UndoUnmarkCommandParser +activate UndoUnmarkCommandParser + +UndoUnmarkCommandParser --> HealthcareXpressParser +deactivate UndoUnmarkCommandParser + +HealthcareXpressParser -> UndoUnmarkCommandParser : parse("userInput") +activate UndoUnmarkCommandParser + +create UndoUnmarkCommand +UndoUnmarkCommandParser -> UndoUnmarkCommand +activate UndoUnmarkCommand + +UndoUnmarkCommand --> UndoUnmarkCommandParser : m +deactivate UndoUnmarkCommand + +UndoUnmarkCommandParser --> HealthcareXpressParser : m +deactivate UndoUnmarkCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UndoUnmarkCommandParser -[hidden]-> HealthcareXpressParser +destroy UndoUnmarkCommandParser + +HealthcareXpressParser --> LogicManager : m +deactivate HealthcareXpressParser + +LogicManager -> UndoUnmarkCommand : execute() +activate UndoUnmarkCommand + +Create InternalEditor +UndoUnmarkCommand -> InternalEditor +activate InternalEditor + +InternalEditor --> UndoUnmarkCommand +deactivate InternalEditor + +UndoUnmarkCommand -> InternalEditor : editPatient() +activate InternalEditor + +InternalEditor -> Model : setPerson() +activate Model + +Model --> InternalEditor +deactivate Model + +deactivate InternalEditor + +create CommandResult +UndoUnmarkCommand -> CommandResult +activate CommandResult + +CommandResult --> UndoUnmarkCommand +deactivate CommandResult + +UndoUnmarkCommand --> LogicManager : result +deactivate UndoUnmarkCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/UnmarkActivityDiagram.puml b/docs/diagrams/UnmarkActivityDiagram.puml new file mode 100644 index 00000000000..a9f2bdcc7cf --- /dev/null +++ b/docs/diagrams/UnmarkActivityDiagram.puml @@ -0,0 +1,21 @@ +@startuml +!include style.puml + +start +:User executes mark command; + +if () then ([uid and dateslot index are both present]) + if () then ([uid refers to a valid patient]) + if () then ([dateslot index is valid]) + :Unmark the dateslot at the index for the patient with uid; + else ([else]) + :Show error message about invalid dateslot index; + endif + else ([else]) + :Show error message about invalid uid; + endif +else ([else]) + :Show error message about missing uid and/or missing dateslot index; +endif +stop +@enduml diff --git a/docs/diagrams/UnmarkSequenceDiagram.puml b/docs/diagrams/UnmarkSequenceDiagram.puml new file mode 100644 index 00000000000..0e45b995e02 --- /dev/null +++ b/docs/diagrams/UnmarkSequenceDiagram.puml @@ -0,0 +1,84 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HealthcareXpressParser" as HealthcareXpressParser LOGIC_COLOR +participant ":UnmarkCommandParser" as UnmarkCommandParser LOGIC_COLOR +participant "m:UnmarkCommand" as UnmarkCommand LOGIC_COLOR +participant ":InternalEditor" as InternalEditor LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("unmark userInput") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("unmark userInput") +activate HealthcareXpressParser + +create UnmarkCommandParser +HealthcareXpressParser -> UnmarkCommandParser +activate UnmarkCommandParser + +UnmarkCommandParser --> HealthcareXpressParser +deactivate UnmarkCommandParser + +HealthcareXpressParser -> UnmarkCommandParser : parse("userInput") +activate UnmarkCommandParser + +create UnmarkCommand +UnmarkCommandParser -> UnmarkCommand +activate UnmarkCommand + +UnmarkCommand --> UnmarkCommandParser : m +deactivate UnmarkCommand + +UnmarkCommandParser --> HealthcareXpressParser : m +deactivate UnmarkCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UnmarkCommandParser -[hidden]-> HealthcareXpressParser +destroy UnmarkCommandParser + +HealthcareXpressParser --> LogicManager : m +deactivate HealthcareXpressParser + +LogicManager -> UnmarkCommand : execute() +activate UnmarkCommand + +Create InternalEditor +UnmarkCommand -> InternalEditor +activate InternalEditor + +InternalEditor --> UnmarkCommand +deactivate InternalEditor + +UnmarkCommand -> InternalEditor : editPatient() +activate InternalEditor + +InternalEditor -> Model : setPerson() +activate Model + +Model --> InternalEditor +deactivate Model + +deactivate InternalEditor + +create CommandResult +UnmarkCommand -> CommandResult +activate CommandResult + +CommandResult --> UnmarkCommand +deactivate CommandResult + +UnmarkCommand --> LogicManager : result +deactivate UnmarkCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/UpdateContactActivityDiagram.puml b/docs/diagrams/UpdateContactActivityDiagram.puml new file mode 100644 index 00000000000..27550c1aa05 --- /dev/null +++ b/docs/diagrams/UpdateContactActivityDiagram.puml @@ -0,0 +1,21 @@ +@startuml +start +:User executes update contact command; + +if () then ([all fields are present with exactly 1 input]) + if () then ([person to edit is a patient]) + if () then ([contact category is Next of Kin or Physician]) + :Update contact and return; + else ([else]) + :Show error message about invalid contact category; + endif + else ([else]) + :Show error message about invalid category; + endif +else ([else]) +:Show error message about invalid command format; +endif +stop +@enduml + + diff --git a/docs/diagrams/UpdateContactSequenceDiagram.puml b/docs/diagrams/UpdateContactSequenceDiagram.puml new file mode 100644 index 00000000000..6f50312e021 --- /dev/null +++ b/docs/diagrams/UpdateContactSequenceDiagram.puml @@ -0,0 +1,55 @@ +@startuml + +participant ":LogicManager" as LogicManager +participant ":HealthcareXpressParser" as HealthcareXpressParser +participant ":UpdateContactCommandParser" as UpdateContactCommandParser +participant ":UpdateContactCommand" as UpdateContactCommand +participant ":CommandResult" as CommandResult + +participant ":Model" as Model + +[-> LogicManager : execute("updatecontact id/3 n/ John Doe p/ 81234567 e/ johndoe@example.com c/ D") +activate LogicManager + +LogicManager -> HealthcareXpressParser : parseCommand("updatecontact...") +activate HealthcareXpressParser + +create UpdateContactCommandParser +HealthcareXpressParser -> UpdateContactCommandParser +activate UpdateContactCommandParser + +UpdateContactCommandParser --> HealthcareXpressParser + +HealthcareXpressParser -> UpdateContactCommandParser : parse("updatecontact...") + +create UpdateContactCommand +UpdateContactCommandParser --> UpdateContactCommand +activate UpdateContactCommand +UpdateContactCommand --> UpdateContactCommandParser +UpdateContactCommandParser --> HealthcareXpressParser : UpdateContactCommand +deactivate UpdateContactCommandParser +HealthcareXpressParser --> LogicManager : UpdateContactCommand +deactivate HealthcareXpressParser + +LogicManager --> UpdateContactCommand : execute() +UpdateContactCommand --> Model: getFilteredPersonsList() +activate Model +Model --> UpdateContactCommand +create BasePerson +UpdateContactCommand --> BasePerson +activate BasePerson +BasePerson --> UpdateContactCommand +UpdateContactCommand --> Model : updateFilteredPersonsList(BasePerson) +Model --> UpdateContactCommand +deactivate BasePerson +deactivate Model + +create CommandResult +UpdateContactCommand --> CommandResult +CommandResult --> UpdateContactCommand +UpdateContactCommand --> LogicManager : CommandResult +deactivate UpdateContactCommand +<- LogicManager : CommandResult + +@enduml + diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..2b6c0e46359 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -2,7 +2,7 @@ !include ../style.puml Participant ":LogicManager" as logic LOGIC_COLOR -Participant ":AddressBookParser" as abp LOGIC_COLOR +Participant ":HealthcareXpressParser" as abp LOGIC_COLOR Participant ":EditCommandParser" as ecp LOGIC_COLOR Participant "command:EditCommand" as ec LOGIC_COLOR diff --git a/docs/images/AddNurse.png b/docs/images/AddNurse.png new file mode 100644 index 00000000000..1460306d487 Binary files /dev/null and b/docs/images/AddNurse.png differ diff --git a/docs/images/AddPatient.png b/docs/images/AddPatient.png new file mode 100644 index 00000000000..df127ff7708 Binary files /dev/null and b/docs/images/AddPatient.png differ diff --git a/docs/images/AddPatientActivityDiagram.png b/docs/images/AddPatientActivityDiagram.png new file mode 100644 index 00000000000..50de9d664a1 Binary files /dev/null and b/docs/images/AddPatientActivityDiagram.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 00000000000..05c2752648c Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/Assign.png b/docs/images/Assign.png new file mode 100644 index 00000000000..74e6ce8264f Binary files /dev/null and b/docs/images/Assign.png differ diff --git a/docs/images/AssignActivityDiagram.png b/docs/images/AssignActivityDiagram.png new file mode 100644 index 00000000000..28a29b9c52e Binary files /dev/null and b/docs/images/AssignActivityDiagram.png differ diff --git a/docs/images/AssignSequenceDiagram.png b/docs/images/AssignSequenceDiagram.png new file mode 100644 index 00000000000..561024e9d00 Binary files /dev/null and b/docs/images/AssignSequenceDiagram.png differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png index c08c13f5c8b..e4cc7bebb18 100644 Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ diff --git a/docs/images/Deassign.png b/docs/images/Deassign.png new file mode 100644 index 00000000000..f72aa21050b Binary files /dev/null and b/docs/images/Deassign.png differ diff --git a/docs/images/Delete.png b/docs/images/Delete.png new file mode 100644 index 00000000000..7880a14c9a2 Binary files /dev/null and b/docs/images/Delete.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..3fcca6c5b37 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Edit.png b/docs/images/Edit.png new file mode 100644 index 00000000000..16831790dec Binary files /dev/null and b/docs/images/Edit.png differ diff --git a/docs/images/Find.png b/docs/images/Find.png new file mode 100644 index 00000000000..ca92e2ed6b4 Binary files /dev/null and b/docs/images/Find.png differ diff --git a/docs/images/Help.png b/docs/images/Help.png new file mode 100644 index 00000000000..fd32f316660 Binary files /dev/null and b/docs/images/Help.png differ diff --git a/docs/images/HelpQuickCommandHelp.png b/docs/images/HelpQuickCommandHelp.png new file mode 100644 index 00000000000..a9a8bc6a082 Binary files /dev/null and b/docs/images/HelpQuickCommandHelp.png differ diff --git a/docs/images/ListActivityDiagram.png b/docs/images/ListActivityDiagram.png new file mode 100644 index 00000000000..20752a17e30 Binary files /dev/null and b/docs/images/ListActivityDiagram.png differ diff --git a/docs/images/ListNurse.png b/docs/images/ListNurse.png new file mode 100644 index 00000000000..8ae58bb1aaf Binary files /dev/null and b/docs/images/ListNurse.png differ diff --git a/docs/images/ListPatientheartDiasease.png b/docs/images/ListPatientheartDiasease.png new file mode 100644 index 00000000000..1a556a9fd78 Binary files /dev/null and b/docs/images/ListPatientheartDiasease.png differ diff --git a/docs/images/ListSequenceDiagram.png b/docs/images/ListSequenceDiagram.png new file mode 100644 index 00000000000..cc6bfc1317e Binary files /dev/null and b/docs/images/ListSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..e38ec134841 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MarkActivityDiagram.png b/docs/images/MarkActivityDiagram.png new file mode 100644 index 00000000000..a83ad39addc Binary files /dev/null and b/docs/images/MarkActivityDiagram.png differ diff --git a/docs/images/MarkSequenceDiagram.png b/docs/images/MarkSequenceDiagram.png new file mode 100644 index 00000000000..5da4287b7b6 Binary files /dev/null and b/docs/images/MarkSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..496b35d45b2 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index e7b4c8880cd..442e0ca3778 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/PatientClassDiagram.png b/docs/images/PatientClassDiagram.png new file mode 100644 index 00000000000..441a0f2720f Binary files /dev/null and b/docs/images/PatientClassDiagram.png differ diff --git a/docs/images/PersonClassDiagram.png b/docs/images/PersonClassDiagram.png new file mode 100644 index 00000000000..c1958630c76 Binary files /dev/null and b/docs/images/PersonClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..d018dfd6e82 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiV1.3.png b/docs/images/UiV1.3.png new file mode 100644 index 00000000000..324ea6f5b55 Binary files /dev/null and b/docs/images/UiV1.3.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png index 8f7538cd884..fa8a0e78466 100644 Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png index df9908d0948..98ff69b1a13 100644 Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 36519c1015b..f3cb19af548 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 19959d01712..17846041445 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png index 4c623e4f2c5..c3e79c32f7e 100644 Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png index 84ad2afa6bd..bf16b94cae0 100644 Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index 6addcd3a8d9..5b5c0f777c5 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/UndoUnmark.png b/docs/images/UndoUnmark.png new file mode 100644 index 00000000000..c527045edf2 Binary files /dev/null and b/docs/images/UndoUnmark.png differ diff --git a/docs/images/UndoUnmarkActivityDiagram.png b/docs/images/UndoUnmarkActivityDiagram.png new file mode 100644 index 00000000000..28e24fcfbc3 Binary files /dev/null and b/docs/images/UndoUnmarkActivityDiagram.png differ diff --git a/docs/images/UndoUnmarkSequenceDiagram.png b/docs/images/UndoUnmarkSequenceDiagram.png new file mode 100644 index 00000000000..dd556b50f11 Binary files /dev/null and b/docs/images/UndoUnmarkSequenceDiagram.png differ diff --git a/docs/images/Unmark.png b/docs/images/Unmark.png new file mode 100644 index 00000000000..f3463a7cf2c Binary files /dev/null and b/docs/images/Unmark.png differ diff --git a/docs/images/UnmarkActivityDiagram.png b/docs/images/UnmarkActivityDiagram.png new file mode 100644 index 00000000000..b871b3095b4 Binary files /dev/null and b/docs/images/UnmarkActivityDiagram.png differ diff --git a/docs/images/UnmarkSequenceDiagram.png b/docs/images/UnmarkSequenceDiagram.png new file mode 100644 index 00000000000..0801c272e9e Binary files /dev/null and b/docs/images/UnmarkSequenceDiagram.png differ diff --git a/docs/images/UpdateContactActivityDiagram.png b/docs/images/UpdateContactActivityDiagram.png new file mode 100644 index 00000000000..05634277606 Binary files /dev/null and b/docs/images/UpdateContactActivityDiagram.png differ diff --git a/docs/images/UpdateContactSequenceDiagram.png b/docs/images/UpdateContactSequenceDiagram.png new file mode 100644 index 00000000000..1a9e5ad2711 Binary files /dev/null and b/docs/images/UpdateContactSequenceDiagram.png differ diff --git a/docs/images/checkSimilar.png b/docs/images/checkSimilar.png new file mode 100644 index 00000000000..5a762b64b02 Binary files /dev/null and b/docs/images/checkSimilar.png differ diff --git a/docs/images/helpMessageUpdated.png b/docs/images/helpMessageUpdated.png new file mode 100644 index 00000000000..e1fed3e6283 Binary files /dev/null and b/docs/images/helpMessageUpdated.png differ diff --git a/docs/images/johnbenedictyan.png b/docs/images/johnbenedictyan.png new file mode 100644 index 00000000000..8286aaedaee Binary files /dev/null and b/docs/images/johnbenedictyan.png differ diff --git a/docs/images/lolfoollors.png b/docs/images/lolfoollors.png new file mode 100644 index 00000000000..6f12aed4c59 Binary files /dev/null and b/docs/images/lolfoollors.png differ diff --git a/docs/images/mlzt2000.png b/docs/images/mlzt2000.png new file mode 100644 index 00000000000..969d68fc38b Binary files /dev/null and b/docs/images/mlzt2000.png differ diff --git a/docs/images/updatecontact.png b/docs/images/updatecontact.png new file mode 100644 index 00000000000..8a5e225db39 Binary files /dev/null and b/docs/images/updatecontact.png differ diff --git a/docs/images/xhphoong.png b/docs/images/xhphoong.png new file mode 100644 index 00000000000..02248ad6382 Binary files /dev/null and b/docs/images/xhphoong.png differ diff --git a/docs/images/yeehaoo.png b/docs/images/yeehaoo.png new file mode 100644 index 00000000000..9ef4e97f0a9 Binary files /dev/null and b/docs/images/yeehaoo.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..1728d50d133 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: HealthcareXpress --- [![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) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103-F13-4/tp/branch/master/graph/badge.svg?token=C354PCI8TZ)](https://codecov.io/gh/AY2223S1-CS2103-F13-4/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). +**HealthcareXpess is a desktop application for medical administrators to manage patients and nurses.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using HealthcareXpress, head over to the [_Quick Start_ section of the **User Guide**](https://ay2223s1-cs2103-f13-4.github.io/tp/UserGuide.html)). +* If you are interested about developing HealthcareXpress, the [**Developer Guide**](https://ay2223s1-cs2103-f13-4.github.io/tp/DeveloperGuide.html)) is a good place to start. **Acknowledgements** diff --git a/docs/team/johnbenedictyan.md b/docs/team/johnbenedictyan.md new file mode 100644 index 00000000000..c5849a808f2 --- /dev/null +++ b/docs/team/johnbenedictyan.md @@ -0,0 +1,109 @@ +--- +layout: page +title: John Benedict's Project Portfolio Page +--- + +### Project: HealthCare Xpress + +HealthCare Xpress is a desktop application that is to be used by medical administrator for managing patients that require home-visits and nurses. It helps medical administrators manage nurses, patients, their next of kin and attending physicians.The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has 10042 lines of code. + +Given below are my contributions to the project. + +- **New Features**: + + 1. Uid + + - What it does: + - Unique Id and the manager to manage operations associated with the uid. + - Justification: + - Uid solves the issue whereby the medical administrator may enter very similar persons into Healthcare Xpress. + - Uid helps to differentiate between these individuals. + - Highlights: + - Implementing a unique set of uid was challenging. +
+ + 1. Delete Command + + - What it does: + - Deletes the person based on their uid. + - Justification: + - The delete command is an essential command so that the medical administrator can remove persons who are no longer relevant. +
+ + 3. Check Similar Command + + - What it does: + - Deletes the person based on their uid. + - Justification: + - The delete command is an essential command so that the medical administrator can remove persons who are no longer relevant. +
+ +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=ay2223s1-cs2103-f13-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&tabType=authorship&tabAuthor=johnbenedictyan&tabRepo=AY2223S1-CS2103-F13-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +- **Project management**: + + - Assigned teammates to different issues and kept track of their progress +
+ +- **Enhancements to existing features**: + + - Refactor code to use streams and optional. + - Refactor code to increase SLAP [#126](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/126) [#224](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/224) + - Improve List Parser to account for commonly misspelt words [#111](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/111) +
+ +- **Documentation**: + + - User Guide: + - Delete Command + - Other miscellaneous parts and proofreading + - Developer Guide: + - Target user profile + - Value proposition + - User stories + - Delete command implementation + - Unique ID implementation + - Assign Command + - Deassign Command + - Other miscellaneous parts and proofreading +
+ +- **Contributions to the Developer Guide** + ### Assign Feature + + #### Motivation: + + - The assign feature is necessary so that the medical administrator can visually see which nurse is attending which patient's home visit. + + #### Implementation: + + ![AssignSequenceDiagram](../images/AssignSequenceDiagram.png) + + Step 1. The user executes `assign id/3 id/2` + + Step 2. `HealthcareXpressParser` creates an `AssignCommandParser` to parse the arguments. + + Step 3. `AssignCommandParser` checks validity of the given arguments and creates an `AssignCommand`. + + Step 4. The `AssignCommand` is executed, and a new `InternalEditor` is created. + + Step 5. `AssignCommand` calls the `InternalEditor`'s methods of `editPatient` and `editNurse`. + + Step 6. `Model` updates the database, and displays all the persons. + + The activity diagram below summarises exception handling of AssignCommand: + + ![AssignActivityDiagram](../images/AssignActivityDiagram.png) +
+ + #### Design considerations: + + - **Aspect: How the parse interprets the order of uids** + - **Alternative 1:** Fix the order of the uid, so patient then nurse + - Pros: There will be less checking needed to deduce the class of the persons involved. + - Cons: The user experience will suffer as the medical administrator might not be able to accurately remember which uid corresponding to which person, the nurse or the patient. +
+ - **Alternative 2:** Have no fix order, as long as one nurse uid and one patient uid is inputted + - Pros: The user experience will be better as there will be more leeway. + - Cons: Harder to implement and more testing is required. +
diff --git a/docs/team/lolfoollors.md b/docs/team/lolfoollors.md new file mode 100644 index 00000000000..82e8b599950 --- /dev/null +++ b/docs/team/lolfoollors.md @@ -0,0 +1,53 @@ +--- +layout: page +title: lolfoollors's Project Portfolio Page +--- + +### Project: HealthCare Xpress + +HealthCare Xpress is a desktop application that is to be used by medical administrator for managing patients that require home-visits and nurses. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 16 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to add nurse. + * What it does: It allows the app to add nurses into HealthCare Xpress. + * Justification: This is critical in letting the application run as intended. The medical administrator would be able to add nurse and view which nurse might be available for different patients. + * Highlights: This feature was done mainly by refactoring existing code + +* **Major Enhancement**: Overhauled the entire help window. + * What it does: It allows the user to look in the help window to understand the functions more. + * Justification: It provides a quick and easily access to a help book in case of emergency when there is no internet available. Furthermore, it also provides a sample input and output to show to the user how to use it and the expected outcome. + * Highlights: + * There is a mini app display to show sample input and output for the command selected. + * The text is animated to show the user how it is typed in. + * There is a search bar for the user to search for the command easily. + * There is a link to our GitHub page and User guide if the user still does not understand how to use it. + * There is a help command at the bottom right to show more details on how to use it. + * Operates in a different thread so you need not wait for the animation to finish before using the app. + +* **Minor Enhancements**: Overhauled Date checking for date class. + * What it does: It now not only checks for valid date format but also checks for leap year and valid dates. + * Justification: It is critical to Healthcare Xpress working properly as date can only be written in 1 format. If written other than designated format before, the app will just totally ignore the user. + * Highlights: + * Now checks for valid dates properly. + +* **Minor Enhancements**: Updated GUI of the app. + * What it does: Improved looks of program. + * Justification: To differentiate from other project applications. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=ay2223s1-cs2103-f13-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&tabType=authorship&tabAuthor=LolfoollorS&tabRepo=AY2223S1-CS2103-F13-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Code Review + * Testing of Code + +* **Documentation**: + * User Guide: + * Add Nurse + * Help Window + * Fixed documentation bugs and edits. [#220](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/220/files) [#246](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/246) + +* **Community**: + * Reported 15 bugs during PE-D exercise. + + diff --git a/docs/team/mlzt2000.md b/docs/team/mlzt2000.md new file mode 100644 index 00000000000..bb85460dcdb --- /dev/null +++ b/docs/team/mlzt2000.md @@ -0,0 +1,38 @@ +### Project: Healthcare Xpress + +Healthcare Xpress is a desktop medical address book application used by medical administrators, for managing medical staff and patients. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10000 lines of code. + +Given below are my contributions to the project. + +* **New Feature: Mark [Defunct], replaced by `unmark` and `undounmark`** + * What it does: Marks a patient as visited. + * Justification: A core functionality of the application is to show that patients have been visited by their assigned nurse. + * Highlights: This command made use of existing code that supported the Edit command, making it a more specific version of the Edit functionality. + * Pull request: [#82](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/82) + + +* **Code Contributed:** [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=low&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&tabType=authorship&tabAuthor=mlzt2000&tabRepo=AY2223S1-CS2103-F13-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Enhancements to existing features: Filtered List [Not part of final product]** + * What it does: Allows user to filter out patients that have not been assigned nurses, and nurses that have not been completely assigned with patients. + * Justification: There is a need to find out which patients have not been assigned so that user does not miss out on any patients. There is then a need to find nurses that are still available to conduct home visits. + * Pull request: [#129](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/129) + + +* **Contributions to team-based tasks**: + * Code reviews. + * Overall testing of code. + + +* **Documentation**: + * User Guide: + * Mark Command [Defunct], replaced by `unmark` and `undounmark`. + * Developer Guide: + * Non-functional requirements. + * Mark Command [Defunct], replaced by `unmark` and `undounmark`. + + +* **Community**: + * Reported bugs during PE-D. + diff --git a/docs/team/xhphoong.md b/docs/team/xhphoong.md new file mode 100644 index 00000000000..13005dc9e9c --- /dev/null +++ b/docs/team/xhphoong.md @@ -0,0 +1,65 @@ +--- +layout: page +title: xhphoong's Project Portfolio Page +--- + +### Project: HealthCare Xpress + +Healthcare Xpress is a desktop medical address book application used by medical administrators, for managing medical staff and patients. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10k lines of code. + +Given below are my contributions to the project. + +* **New Features**: + +1. Assign a Patient's dateslot(s) to a nurse + * What it does: Allows the medical administrator to assign a patient's dateslot(s) to a nurse. + * Justification: This is the main feature of our product. The medical administrator assign the patient's dateslot(s) to nurse and the system will record down the assign status of each date slot and the homevisits that the nurses have to attend to. The system will also auto check time crashes. This makes the administrative assigning process more easy and effective. + * Related Pull Requests: [#239](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/239) [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) + +2. Deassign a Patient's dateslot(s) / a Nurse's homevisit(s) + * What it does: Allows the medical administrator to deassign a patient's dateslot(s) / a nurse's homevisit(s). + * Justification: In case of a sudden change made or wrong assigning made, the medical administrator can deassign the patient's dateslot(s) / the nurse's homevisit(s). + * Related Pull Requests: [#239](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/239) [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) + +3. Unmark a Patient's dateslot as fail to visit + * What it does: Allows the medical administrator to unmark a patient's dateslot that has passed as fail to visit. + * Justification: When the nurse attended to the homevisits but the patient were not there or other circumstance that made the nurse to fail visit the patient, the medical administrator can unmark a patient's dateslot as fail to visit so that the medical administrator can keep track of the visit status of each date slot and determine whether there is a need to schedule a new home visit date and slot with the patient. + * Related Pull Requests: [#239](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/239) [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) + +4. Undo unmark a Patient's dateslot as success visit + * What it does: Allows the medical administrator to undo the unmarking of a patient's dateslot that has passed to success visit. + * Justification: When the medical administrator had unmark the wrong date slot, the medical administrator can undo the unmarking of a patient's dateslot to success visit so that the medical administrator can keep track of the correct visit status of each date slot. + * Related Pull Requests: [#239](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/239) [#123](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/123) + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=xhphoong&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=xhphoong&tabRepo=AY2223S1-CS2103-F13-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Enhancements to existing features**: + +1. Add gender attribute to person. [#67](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/67/files) +2. Add patient that extends from person and has date and slot attribute. [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) [#67](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/67/files) +3. Add home visit, unavailable date list and fully scheduled date list attributes to the nurse (created by other teammates). [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) +4. Modify the add feature to add patient. [#67](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/67/files) +5. Modify the edit feature to edit newly-added attributes such as date and slot, unavailable date and gender. [#239](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/239) [#118](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/118) [#79](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/79) [#67](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/67/files) + +* **Documentation**: + * User Guide: + * Add Patient Feature + * Edit Feature + * Assign Feature + * Deassign Feature + * Unmark Feature + * UndoUnmark Feature + * Related Pull Requests : [#130](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/130) [#122](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/122) + * Developer Guide: + * Use cases [#248](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/248) [#29](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/29) + * Initial Add Command Implementation [#104](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/104) + +* **Contributions to team-based tasks**: + * Setting up the GitHub team organisation and repo + * Code reviews made for other teammates' pull requests + * Fix bugs and errors + * Improve code quality + + + diff --git a/docs/team/yeehaoo.md b/docs/team/yeehaoo.md new file mode 100644 index 00000000000..a869888d127 --- /dev/null +++ b/docs/team/yeehaoo.md @@ -0,0 +1,51 @@ +### Project: Healthcare Xpress + +Healthcare Xpress is a desktop medical address book application used by medical administrators, for managing medical staff and patients. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 8200 lines of code. + +Given below are my contributions to the project. + + + +* **New Feature**: Update Emergency Contact + * What it does: Allows the medical administrator to add and update attending physician and next of kin contacts for patients. + * Justification: In case of emergency, the medical administrator would be able to quickly reach the attending physician and next of kin, and inform them of the emergency. For example, if an incident occurs during a nurse's home visit, the attending physician can be reached within a quick query instead of having to query some other database. + * Highlights: This implementation was challenging to make efficient, and the plan initially was to create two commands to solve this issue. However, it was found that they could be combined through polymorphism instead and the problem was solved efficiently. + * Pull request: [#110](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/110) + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=ay2223s1-cs2103-f13-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&tabType=authorship&tabAuthor=yeehaoo&tabRepo=AY2223S1-CS2103-F13-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Enhancements to existing features**: Filtered List + * What it does: Allows the medical administrator to filter enrolled patients or nurses by several filters, including category, gender, tags and address. + * Justification: This feature allows the medical administrator to quickly filter the list of people based on given specifications, to facilitate other tasks such as assigning patients to nurses based on location, or other such queries. + * Highlights: The implementation took some time because instead of creating a new function, the current implementation had to be examined and adapted to our use case. Once the current mechanism was understood, then the tweaks were applied. + * Pull request: [#68](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/68) + + +* **Contributions to team-based tasks**: + * Managed issues and milestones + * Handled deadlines, frequent check-ins with team + * Code reviews for many pull requests + + +* **Documentation**: + * User Guide: + * List feature + * Update emergency contact feature + * Help feature + * Find feature + * Enhance overall grammar and readability [#230](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/230) + * Developer Guide: + * List feature + * Update emergency contact feature + * Edited add feature's comments [#247](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/247) + * Update hyperlinks [#245](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/245) + * Update DG diagrams [#247](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/247) + * Person class diagram [#249](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/249) + * Enhance overall grammar and readability [#249](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/249) + + +* **Community**: + * PRs reviewed: [#29](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/29), [#32](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/32), [#44](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/44), [#75](https://github.com/AY2223S1-CS2103-F13-4/tp/pull/75) + * Reported bugs during PE-D exercise. diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 880c701042f..09e24328acb 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -43,7 +43,7 @@ public class RemarkCommand extends Command { ### Hook `RemarkCommand` into the application -Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`. +Now that we have our `RemarkCommand` ready to be executed, we need to update `HealthcareXpressParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`. You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee). @@ -216,7 +216,7 @@ public RemarkCommand parse(String args) throws ParseException {
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`! +:information_source: Don’t forget to update `HealthcareXpressParser` to use our new `RemarkCommandParser`!
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..ee9f3b5edbd 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -120,7 +120,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ CommandResult commandResult; //Parse user input from String to a Command - Command command = addressBookParser.parseCommand(commandText); + Command command = HealthcareXpressParser.parseCommand(commandText); //Executes the Command and stores the result commandResult = command.execute(model); @@ -141,7 +141,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. _Step over_ the logging code since it is of no interest to us now. ![StepOver](../images/tracing/StepOver.png) -1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): +1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `HealthcareXpressParser#parseCommand()` method (partial code given below): ``` java public Command parseCommand(String userInput) throws ParseException { ... diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/address/AppParameters.java index ab552c398f3..56c76c7f73f 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/address/AppParameters.java @@ -18,14 +18,6 @@ public class AppParameters { private Path configPath; - public Path getConfigPath() { - return configPath; - } - - public void setConfigPath(Path configPath) { - this.configPath = configPath; - } - /** * Parses the application command-line parameters. */ @@ -43,6 +35,14 @@ public static AppParameters parse(Application.Parameters parameters) { return appParameters; } + public Path getConfigPath() { + return configPath; + } + + public void setConfigPath(Path configPath) { + this.configPath = configPath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index 052a5068631..1c4908730b9 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -4,17 +4,18 @@ /** * The main entry point to the application. - * + *

* This is a workaround for the following error when MainApp is made the * entry point of the application: - * - * Error: JavaFX runtime components are missing, and are required to run this application - * + *

+ * Error: JavaFX runtime components are missing, and are required to run this + * application + *

* The reason is that MainApp extends Application. In that case, the * LauncherHelper will check for the javafx.graphics module to be present * as a named module. We don't use JavaFX via the module system so it can't * find the javafx.graphics module, and so the launch is aborted. - * + *

* By having a separate main class (Main) that doesn't extend Application * to be the entry point of the application, we avoid this issue. */ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..08a77159db3 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -69,9 +69,12 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s address + * book and {@code userPrefs}.
+ * The data from the sample address book will be used instead if + * {@code storage}'s address book is not found, + * or an empty address book will be used instead if errors occur when reading + * {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { Optional addressBookOptional; @@ -124,7 +127,8 @@ protected Config initConfig(Path configFilePath) { initializedConfig = new Config(); } - //Update config file in case it was missing to begin with or there are new/unused fields + // Update config file in case it was missing to begin with or there are + // new/unused fields try { ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); } catch (IOException e) { @@ -134,7 +138,8 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs + * file path, * or a new {@code UserPrefs} with default configuration if errors occur when * reading from the file. */ @@ -155,7 +160,8 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = new UserPrefs(); } - //Update prefs file in case it was missing to begin with or there are new/unused fields + // Update prefs file in case it was missing to begin with or there are + // new/unused fields try { storage.saveUserPrefs(initializedPrefs); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 91145745521..8de795addfa 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -37,7 +37,7 @@ public boolean equals(Object other) { if (other == this) { return true; } - if (!(other instanceof Config)) { //this handles null as well. + if (!(other instanceof Config)) { // this handles null as well. return false; } @@ -55,8 +55,8 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Current log level : " + logLevel); - sb.append("\nPreference file Location : " + userPrefsFilePath); + sb.append("Current log level : ").append(logLevel); + sb.append("\nPreference file Location : ").append(userPrefsFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index ba33653be67..414b62b3b7a 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -27,7 +27,8 @@ public GuiSettings() { } /** - * Constructs a {@code GuiSettings} with the specified height, width and position. + * Constructs a {@code GuiSettings} with the specified height, width and + * position. */ public GuiSettings(double windowWidth, double windowHeight, int xPosition, int yPosition) { this.windowWidth = windowWidth; @@ -52,7 +53,7 @@ public boolean equals(Object other) { if (other == this) { return true; } - if (!(other instanceof GuiSettings)) { //this handles null as well. + if (!(other instanceof GuiSettings)) { // this handles null as well. return false; } @@ -71,9 +72,9 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Width : " + windowWidth + "\n"); - sb.append("Height : " + windowHeight + "\n"); - sb.append("Position : " + windowCoordinates); + sb.append("Width : ").append(windowWidth).append("\n"); + sb.append("Height : ").append(windowHeight).append("\n"); + sb.append("Position : ").append(windowCoordinates); return sb.toString(); } } diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 431e7185e76..fdee8761f32 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -11,23 +11,27 @@ /** * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
- * These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * These loggers have been configured to output messages to the console and a + * {@code .log} file by default, + * at the {@code INFO} level. A new {@code .log} file with a new numbering will + * be created after the log + * file reaches 5MB big, up to a maximum of 5 files.
*/ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB private static final String LOG_FILE = "addressbook.log"; private static Level currentLogLevel = Level.INFO; - private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; private static ConsoleHandler consoleHandler; + private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); /** * Initializes with a custom log level (specified in the {@code config} object) - * Loggers obtained *AFTER* this initialization will have their logging level changed
- * Logging levels for existing loggers will only be updated if the logger with the same name + * Loggers obtained *AFTER* this initialization will have their logging level + * changed
+ * Logging levels for existing loggers will only be updated if the logger with + * the same name * is requested again from the LogsCenter. */ public static void init(Config config) { @@ -95,6 +99,7 @@ private static void addFileHandler(Logger logger) { /** * Creates a {@code FileHandler} for the log file. + * * @throws IOException if there are problems opening the file. */ private static FileHandler createFileHandler() throws IOException { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..1bdfdca6f29 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,13 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_UID = "The person uid provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_CATEGORY = "Invalid category detected!"; + public static final String MESSAGE_UPDATECONTACT_INVALID_CATEGORY = "Contact info can only be set for patients."; + public static final String MESSAGE_UPDATECONTACT_INVALID_CONTACT_CATEGORY = + "Contact must be a physician (category D) or next of kin (category K)"; + } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index 12142ec1e32..c9511d13eff 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -32,24 +32,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) { this.isEarlyAccess = isEarlyAccess; } - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - public boolean isEarlyAccess() { - return isEarlyAccess; - } - /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ @@ -64,7 +49,23 @@ public static Version fromString(String versionString) throws IllegalArgumentExc return new Version(Integer.parseInt(versionMatcher.group(1)), Integer.parseInt(versionMatcher.group(2)), Integer.parseInt(versionMatcher.group(3)), - versionMatcher.group(4) == null ? false : true); + versionMatcher.group(4) != null); + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; } @JsonValue 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 19536439c09..edfe6ebe7c0 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -2,14 +2,18 @@ /** * Represents a zero-based or one-based index. - * - * {@code Index} should be used right from the start (when parsing in a new user input), so that if the current - * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what - * base the other component is using for its index. However, after receiving the {@code Index}, that component can - * convert it back to an int if the index will not be passed to a different component again. + *

+ * {@code Index} should be used right from the start (when parsing in a new user + * input), so that if the current + * component wants to communicate with another component, it can send an + * {@code Index} to avoid having to know what + * base the other component is using for its index. However, after receiving the + * {@code Index}, that component can + * convert it back to an int if the index will not be passed to a different + * component again. */ public class Index { - private int zeroBasedIndex; + private final int zeroBasedIndex; /** * Index can only be created by calling {@link Index#fromZeroBased(int)} or @@ -23,14 +27,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. */ @@ -45,10 +41,19 @@ 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) { return other == this // short circuit if same object || (other instanceof Index // instanceof handles nulls - && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check + && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check } + } diff --git a/src/main/java/seedu/address/commons/core/index/ReverseIndexComparator.java b/src/main/java/seedu/address/commons/core/index/ReverseIndexComparator.java new file mode 100644 index 00000000000..7e20d43f212 --- /dev/null +++ b/src/main/java/seedu/address/commons/core/index/ReverseIndexComparator.java @@ -0,0 +1,21 @@ +package seedu.address.commons.core.index; + +import java.util.Comparator; + +/** + * Represents a comparator to compare index number to give a reverse order from + * high to low. + */ +public class ReverseIndexComparator implements Comparator { + + /** + * Compare the index number. + * + * @param firstIndex the first index to be compared. + * @param secondIndex the second index to be compared. + */ + public int compare(Index firstIndex, Index secondIndex) { + return -1 * Integer.compare(firstIndex.getZeroBased(), secondIndex.getZeroBased()); + } + +} diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c..94adaff5f50 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -5,15 +5,17 @@ */ public class IllegalValueException extends Exception { /** - * @param message should contain relevant information on the failed constraint(s) + * @param message should contain relevant information on the failed + * constraint(s) */ public IllegalValueException(String message) { super(message); } /** - * @param message should contain relevant information on the failed constraint(s) - * @param cause of the main exception + * @param message should contain relevant information on the failed + * constraint(s) + * @param cause of the main exception */ public IllegalValueException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/address/commons/util/AppUtil.java index 87aa89c0326..0b50a7421e4 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/address/commons/util/AppUtil.java @@ -19,7 +19,8 @@ public static Image getImage(String imagePath) { } /** - * Checks that {@code condition} is true. Used for validating arguments to methods. + * Checks that {@code condition} is true. Used for validating arguments to + * methods. * * @throws IllegalArgumentException if {@code condition} is false. */ @@ -30,9 +31,11 @@ public static void checkArgument(Boolean condition) { } /** - * Checks that {@code condition} is true. Used for validating arguments to methods. + * Checks that {@code condition} is true. Used for validating arguments to + * methods. * - * @throws IllegalArgumentException with {@code errorMessage} if {@code condition} is false. + * @throws IllegalArgumentException with {@code errorMessage} if + * {@code condition} is false. */ public static void checkArgument(Boolean condition, String errorMessage) { if (!condition) { diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index eafe4dfd681..35c4f36b37c 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -12,14 +12,17 @@ */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); } /** - * Throws NullPointerException if {@code items} or any element of {@code items} is null. + * Throws NullPointerException if {@code items} or any element of {@code items} + * is null. */ public static void requireAllNonNull(Collection items) { requireNonNull(items); diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd9..4de0598ac07 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -18,8 +18,10 @@ public static boolean isFileExists(Path file) { } /** - * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, + * Returns true if {@code path} can be converted into a {@code Path} via + * {@link Paths#get(String)}, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -32,7 +34,9 @@ public static boolean isValidPath(String path) { } /** - * Creates a file if it does not exist along with its missing parent directories. + * Creates a file if it does not exist along with its missing parent + * directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { @@ -42,7 +46,8 @@ public static void createIfMissing(Path file) throws IOException { } /** - * Creates a file if it does not exist along with its missing parent directories. + * Creates a file if it does not exist along with its missing parent + * directories. */ public static void createFile(Path file) throws IOException { if (Files.exists(file)) { diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055d..d2eb9db0d38 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -30,7 +30,7 @@ public class JsonUtil { private static final Logger logger = LogsCenter.getLogger(JsonUtil.class); - private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() + private static final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) @@ -49,10 +49,14 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject } /** - * Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found. - * If any values are missing from the file, default values will be used, as long as the file is a valid json file. - * @param filePath cannot be null. - * @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here. + * Returns the Json object from the given file or {@code Optional.empty()} + * object if the file is not found. + * If any values are missing from the file, default values will be used, as long + * as the file is a valid json file. + * + * @param filePath cannot be null. + * @param classOfObjectToDeserialize Json file has to correspond to the + * structure in the class given here. * @throws DataConversionException if the file format is not as expected. */ public static Optional readJsonFile( @@ -79,6 +83,7 @@ public static Optional readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -90,9 +95,9 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio serializeObjectToJsonFile(filePath, jsonFile); } - /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -102,8 +107,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string - * @param The generic type to create an instance of + * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static String toJsonString(T instance) throws JsonProcessingException { @@ -128,7 +134,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) { * Gets the logging level that matches loggingLevelString *

* Returns null if there are no matches - * */ private Level getLoggingLevel(String loggingLevelString) { return Level.parse(loggingLevelString); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..cb555f1a94d 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.io.PrintWriter; import java.io.StringWriter; @@ -14,25 +15,27 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:

+     * Ignores case, but a full word match is required.
+     * 
+ * 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 + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { - requireNonNull(sentence); - requireNonNull(word); + requireAllNonNull(sentence, word); String preppedWord = word.trim(); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); - String preppedSentence = sentence; - String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + String[] wordsInPreppedSentence = sentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) .anyMatch(preppedWord::equalsIgnoreCase); @@ -45,14 +48,16 @@ public static String getDetails(Throwable t) { requireNonNull(t); StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); - return t.getMessage() + "\n" + sw.toString(); + return t.getMessage() + "\n" + sw; } /** * Returns true if {@code s} represents a non-zero unsigned integer * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input - * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains + * whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..ec60af76e34 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -16,10 +16,11 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; @@ -30,7 +31,9 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ + /** + * Returns an unmodifiable view of the filtered list of persons + */ ObservableList getFilteredPersonList(); /** diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..e35d08bf830 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -10,7 +10,7 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.HealthcareXpressParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; @@ -26,15 +26,16 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final HealthcareXpressParser healthcareXpressParser; /** - * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. + * Constructs a {@code LogicManager} with the given {@code Model} and + * {@code Storage}. */ public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + healthcareXpressParser = new HealthcareXpressParser(); } @Override @@ -42,7 +43,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = healthcareXpressParser.parseCommand(commandText, model); commandResult = command.execute(model); try { diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..ac0e51cfab8 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,10 +2,14 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -18,24 +22,39 @@ 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 patient/nurse to the address book. " + "Parameters: " + + PREFIX_CATEGORY + "CATEGORY " + PREFIX_NAME + "NAME " + + PREFIX_GENDER + "GENDER " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_TAG + "TAG]... \n" + + "If add patient, you can choose to add details: " + + PREFIX_DATE_AND_SLOT + "HOME_VISIT_DATE_AND_SLOT \n" + + "If add nurse, you can choose to add details: " + + PREFIX_UNAVAILABLE_DATE + "UNAVAILABLE_DATE_TO_HOME_VISIT \n" + "Example: " + COMMAND_WORD + " " + + PREFIX_CATEGORY + "P " + PREFIX_NAME + "John Doe " + + PREFIX_GENDER + "M " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_TAG + "owesMoney " + + PREFIX_DATE_AND_SLOT + "2022-11-11,2"; - 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_INVALID_FIELD_NURSE = "The person to be added is a nurse, " + + "should not have date and slot."; + public static final String MESSAGE_INVALID_FIELD_PATIENT = "The person to be added is a patient, " + + "should not have unavailable date."; + public static final String MESSAGE_SUCCESS = "New %1$s added: %2$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This %1$s already exists in the address book"; + public static final String MESSAGE_SIMILAR_PERSON = "This %1$s is similar someone we found" + + " in the address book: %2$s. We have added them anyways."; private final Person toAdd; /** @@ -51,17 +70,23 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + throw new CommandException(String.format(MESSAGE_DUPLICATE_PERSON, toAdd.getCategoryIndicator())); } + if (model.findSimilarPerson(toAdd).isPresent()) { + model.addPerson(toAdd); + return new CommandResult(String.format(MESSAGE_SIMILAR_PERSON, toAdd.getCategoryIndicator(), + model.findSimilarPerson(toAdd).get())); + } model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.getCategoryIndicator(), toAdd)); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + && toAdd.equals(((AddCommand) other).toAdd)); } + } diff --git a/src/main/java/seedu/address/logic/commands/AssignCommand.java b/src/main/java/seedu/address/logic/commands/AssignCommand.java new file mode 100644 index 00000000000..69c57a05794 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AssignCommand.java @@ -0,0 +1,146 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; + +/** + * Assigns a patient's home-visit dateslot to nurse. + */ +public class AssignCommand extends Command { + + public static final String COMMAND_WORD = "assign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Assign a patient's home visit dateslot to a nurse" + + "by using the unique id number of the patient, nurse and the dateslot index.\n" + + "Parameters: " + PREFIX_UID + "UID of a nurse (must be a positive integer) " + + PREFIX_UID + "UID of a patient (must be a positive integer)" + + " [" + PREFIX_DATE_AND_SLOT_INDEX + "DATE_AND_SLOT_INDEX] \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_UID + "1 " + + PREFIX_UID + "2 " + + PREFIX_DATE_AND_SLOT_INDEX + "1 "; + + public static final String MESSAGE_SUCCESS = "%1$s's dateslot/dateslots assigned to %2$s."; + + public static final String MESSAGE_BOTH_NURSE = "The given uids are both nurses."; + public static final String MESSAGE_BOTH_PATIENT = "The given uids are both patients."; + + private final Uid uid1; + private final Uid uid2; + private final List dateslotIndex; + + /** + * Creates an AssignCommand to assgin specific patient's date slot to a nurse. + */ + public AssignCommand(Uid uid1, Uid uid2, List dateslotIndex) { + requireAllNonNull(uid1, uid2, dateslotIndex); + this.uid1 = uid1; + this.uid2 = uid2; + this.dateslotIndex = new ArrayList<>(); + this.dateslotIndex.addAll(dateslotIndex); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredPersonList(); + Optional person1 = lastShownList.stream().filter(p -> p.getUid().equals(uid1)).findFirst(); + Optional person2 = lastShownList.stream().filter(p -> p.getUid().equals(uid2)).findFirst(); + + if (person1.isEmpty() || person2.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); + } + + Person firstPerson = person1.get(); + Person secondPerson = person2.get(); + Patient patient = getPatient(firstPerson, secondPerson); + Nurse nurse = getNurse(firstPerson, secondPerson); + markAssign(model, patient, nurse); + + return new CommandResult(String.format(MESSAGE_SUCCESS, patient.toLiteString(), nurse.toLiteString())); + } + + private Patient getPatient(Person person1, Person person2) throws CommandException { + Boolean isPerson1Patient = person1.isPatient(); + Boolean isPerson2Patient = person2.isPatient(); + + if (isPerson1Patient && isPerson2Patient) { + throw new CommandException(MESSAGE_BOTH_PATIENT); + } + if (isPerson1Patient || isPerson2Patient) { + return isPerson1Patient + ? (Patient) person1 + : (Patient) person2; + } + throw new CommandException(MESSAGE_BOTH_NURSE); + } + + private Nurse getNurse(Person person1, Person person2) throws CommandException { + Boolean isPerson1Nurse = person1.isNurse(); + Boolean isPerson2Nurse = person2.isNurse(); + + if (isPerson1Nurse && isPerson2Nurse) { + throw new CommandException(MESSAGE_BOTH_NURSE); + } + if (isPerson1Nurse || isPerson2Nurse) { + return isPerson1Nurse + ? (Nurse) person1 + : (Nurse) person2; + } + throw new CommandException(MESSAGE_BOTH_PATIENT); + } + + private void markAssign(Model model, Patient patient, Nurse nurse) throws CommandException { + Long patientUidNo = patient.getUid().getUid(); + Long nurseUidNo = nurse.getUid().getUid(); + List patientDateSlotList = patient.getDatesSlots(); + List nurseHomeVisitList = nurse.getHomeVisits(); + List nurseFullyScheduledList = nurse.getFullyScheduledDates(); + List nurseUnavailableDates = nurse.getUnavailableDates(); + + DateSlotManager marker = new DateSlotManager(patientDateSlotList, this.dateslotIndex); + List updatedDateSlotList = marker.markAssigned(nurseHomeVisitList, nurseUnavailableDates, nurseUidNo); + HomeVisitManager creator = new HomeVisitManager(nurseHomeVisitList, nurseFullyScheduledList); + List updatedHomeVisitList = creator.createHomeVisitList(patientDateSlotList, + this.dateslotIndex, patientUidNo); + List updatedFullyScheduledList = creator.getFullyScheduledDateList(); + + InternalEditor editor = new InternalEditor(model); + editor.editPatient(patient, updatedDateSlotList); + editor.editNurse(nurse, updatedHomeVisitList, updatedFullyScheduledList); + } + + @Override + public boolean equals(Object other) { + if (other instanceof AssignCommand) { + AssignCommand o = (AssignCommand) other; + System.out.println(uid1.equals(o.uid1)); + System.out.println(uid2.equals(o.uid2)); + System.out.println(dateslotIndex.equals(o.dateslotIndex)); + } + return other == this // short circuit if same object + || (other instanceof AssignCommand // instanceof handles nulls + && uid1.equals(((AssignCommand) other).uid1) + && uid2.equals(((AssignCommand) other).uid2) + && dateslotIndex.equals(((AssignCommand) other).dateslotIndex)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/CheckSimilarCommand.java b/src/main/java/seedu/address/logic/commands/CheckSimilarCommand.java new file mode 100644 index 00000000000..f2efd100685 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckSimilarCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Finds and lists all persons in address book whose are similar. + */ +public class CheckSimilarCommand extends Command { + + public static final String COMMAND_WORD = "checkSimilar"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Returns persons that maybe similar"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + ObservableList personList = model.getFilteredPersonList(); + for (int i = 0; i < personList.size(); i++) { + Person curr = personList.get(i); + Optional similarPerson = personList.stream() + .filter(x -> x.isSimilarPerson(curr) && !x.isSamePerson(curr)).findAny(); + if (similarPerson.isPresent()) { + model.updateFilteredPersonList(x -> x.isSamePerson(curr) || x.isSamePerson(similarPerson.get())); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + } + return new CommandResult( + String.format("No similar people found!")); + } + + @Override + public boolean equals(Object other) { + return other == this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..a1fc50a6a1b 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,8 +11,7 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - + public static final String MESSAGE_SUCCESS = "Healthcare Xpress record system has been cleared!"; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..f1b391becf6 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -4,7 +4,8 @@ import seedu.address.model.Model; /** - * Represents a command with hidden internal logic and the ability to be executed. + * Represents a command with hidden internal logic and the ability to be + * executed. */ public abstract class Command { diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..4f06a6069db 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -11,10 +11,14 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; /** diff --git a/src/main/java/seedu/address/logic/commands/DateSlotChecker.java b/src/main/java/seedu/address/logic/commands/DateSlotChecker.java new file mode 100644 index 00000000000..fca5f047cda --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DateSlotChecker.java @@ -0,0 +1,131 @@ +package seedu.address.logic.commands; + +import java.util.List; +import java.util.Optional; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; + +/** + * To check date slot related stuff. + */ +public class DateSlotChecker { + + public static final String MESSAGE_VISITED_DATESLOT = "The date slot %1$s has already passed."; + public static final String MESSAGE_ASSIGNED_DATESLOT = "The date slot %1$s has been assigned already."; + public static final String MESSAGE_TIME_CRASHES = "There is already an existing homevisit in this dateslot %1$s." + + "Please assign another nurse"; + public static final String MESSAGE_UNAVAILABLE_DATE = "The nurse is unavailable on this day %1$s. " + + "Please assign another nurse"; + public static final String MESSAGE_NOT_ASSIGNED_DATESLOT = "The dateslot %1$s has not been assigned."; + public static final String MESSAGE_NOT_VISITED_DATESLOT = "The visit dates has not reached." + + "Cannot unmark it as fail to visit ."; + public static final String MESSAGE_NOT_VISITED_DATESLOT_TWO = "The visit dates has not reached." + + "Cannot undo unmark it as success visit."; + public static final String MESSAGE_SUCCESS_VISIT_DATESLOT = "This dates has already been marked as " + + "success visited. " + "Cannot undo unmark it as success visit."; + + private final DateSlot dateSlot; + + /** + * Create DateSlotChecker. + * @param dateSlot + */ + public DateSlotChecker(DateSlot dateSlot) { + this.dateSlot = dateSlot; + } + + /** + * Check whether the date slot has been visited. + * @throws CommandException if visited + */ + public void checkVisited() throws CommandException { + if (dateSlot.getHasVisited()) { + throw new CommandException(String.format(MESSAGE_VISITED_DATESLOT, + dateSlot.getDateSlotFormatted())); + } + } + + /** + * Check whether the date slot has not been visited. + * @throws CommandException if not visited + */ + public void checkNotVisited() throws CommandException { + if (!dateSlot.getHasVisited()) { + throw new CommandException(MESSAGE_NOT_VISITED_DATESLOT); + } + } + + /** + * Check whether the date slot has not been visited. + * @throws CommandException if not visited + */ + public void checkNotVisitedForUndoUnmark() throws CommandException { + if (!dateSlot.getHasVisited()) { + throw new CommandException(MESSAGE_NOT_VISITED_DATESLOT_TWO); + } + } + + /** + * Check whether the date slot is a success visit + * @throws CommandException if it is a success visit + */ + public void checkSuccessVisited() throws CommandException { + if (dateSlot.getIsSuccessVisit()) { + throw new CommandException(MESSAGE_SUCCESS_VISIT_DATESLOT); + } + } + + /** + * Check whether the date slot has not been assigned + * @throws CommandException if date slot not assigned + */ + public void checkNotAssigned() throws CommandException { + if (!dateSlot.getHasAssigned()) { + throw new CommandException(String.format(MESSAGE_NOT_ASSIGNED_DATESLOT, + dateSlot.getDateSlotFormatted())); + } + } + + /** + * Check whether the date slot has been assigned + * @throws CommandException if date slot is assigned + */ + public void checkAssigned() throws CommandException { + if (dateSlot.getHasAssigned()) { + throw new CommandException(String.format(MESSAGE_ASSIGNED_DATESLOT, dateSlot.getDateSlotFormatted())); + } + } + + /** + * Check whether the date slot has time clash with the homeVisitList. + * @param homeVisitList + * @throws CommandException if there is time clash + */ + public void checkCrashes(List homeVisitList) throws CommandException { + Optional homeVisit = homeVisitList.stream().filter( + h -> h.getDateSlot().getDateTime().equals(dateSlot.getDateTime())).findFirst(); + if (!homeVisit.isEmpty()) { + throw new CommandException(String.format(MESSAGE_TIME_CRASHES, dateSlot.getDateSlotFormatted())); + } + } + + /** + * Check whether the date slot has time clash with the unavailabilityDateList. + * @param unavailabilityDateList + * @throws CommandException if there is time clash + */ + public void checkUnavailability(List unavailabilityDateList) throws CommandException { + Optional date = unavailabilityDateList.stream().filter( + d -> d.getDate().equals(dateSlot.getDate())).findFirst(); + if (!date.isEmpty()) { + throw new CommandException(String.format(MESSAGE_UNAVAILABLE_DATE, dateSlot.getDateSlotFormatted())); + } + } + + +} + + diff --git a/src/main/java/seedu/address/logic/commands/DateSlotManager.java b/src/main/java/seedu/address/logic/commands/DateSlotManager.java new file mode 100644 index 00000000000..0d8245f9075 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DateSlotManager.java @@ -0,0 +1,255 @@ +package seedu.address.logic.commands; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.ReverseIndexComparator; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; + +/** + * A class that manage all date slot related task. + */ +public class DateSlotManager { + + public static final String MESSAGE_OUTOFBOUND_DATESLOT_INDEX = "The date slot index given is out of bounds."; + public final List dateSlotList; + public final List dateSlotIndexList; + + /** + * Construct a DateSlotManager. + * @param dateSlotList + * @param dateSlotIndex + */ + public DateSlotManager(List dateSlotList, List dateSlotIndex) { + this.dateSlotList = new ArrayList<>(); + for (DateSlot dateSlot : dateSlotList) { + this.dateSlotList.add(dateSlot.clone()); + } + this.dateSlotIndexList = new ArrayList<>(dateSlotIndex); + } + + /** + * Construct a DateSlotManger with empty date slot index list. + * @param dateSlotList + */ + public DateSlotManager(List dateSlotList) { + this.dateSlotList = new ArrayList<>(); + for (DateSlot dateSlot : dateSlotList) { + this.dateSlotList.add(dateSlot.clone()); + } + this.dateSlotIndexList = new ArrayList<>(); + } + + /** + * Construct a DateSlotManger with only one index. + * @param dateSlotList + * @param index + */ + public DateSlotManager(List dateSlotList, Index index) { + this.dateSlotList = new ArrayList<>(); + for (DateSlot dateSlot : dateSlotList) { + this.dateSlotList.add(dateSlot.clone()); + } + this.dateSlotIndexList = new ArrayList<>(); + this.dateSlotIndexList.add(index); + } + + /** + * Mark the respective date slot as assigned if there is no time clashes + * @param homeVisitList + * @param unavailableDateList + * @param nurseUidNo + * @return updated dateSlot list + * @throws CommandException + */ + public List markAssigned(List homeVisitList, List unavailableDateList, + Long nurseUidNo) throws CommandException { + if (dateSlotIndexList.isEmpty()) { + markAllAssigned(homeVisitList, unavailableDateList, nurseUidNo); + } else { + markSpecificAssigned(homeVisitList, unavailableDateList, nurseUidNo); + } + return dateSlotList; + } + + private void markAllAssigned(List homeVisitList, List unavailableDateList, + Long nurseUidNo) throws CommandException { + for (DateSlot dateSlot : dateSlotList) { + markAssignedCheck(dateSlot, homeVisitList, unavailableDateList); + dateSlot.mark(nurseUidNo); + } + } + + private void markSpecificAssigned(List homeVisitList, List unavailableDateList, + Long nurseUidNo) throws CommandException { + sortIndex(); + checkIndexOutOfBound(); + for (Index index : dateSlotIndexList) { + DateSlot dateSlot = dateSlotList.get(index.getZeroBased()); + markAssignedCheck(dateSlot, homeVisitList, unavailableDateList); + dateSlot.mark(nurseUidNo); + } + } + + private void markAssignedCheck(DateSlot dateSlot, List homeVisitList, List unavailableDateList) + throws CommandException { + DateSlotChecker checker = new DateSlotChecker(dateSlot); + checker.checkVisited(); + checker.checkAssigned(); + checker.checkCrashes(homeVisitList); + checker.checkUnavailability(unavailableDateList); + } + + private void sortIndex() { + ReverseIndexComparator comp = new ReverseIndexComparator(); + this.dateSlotIndexList.sort(comp); + } + + /** + * Check whether the index given is out of bound. + */ + public void checkIndexOutOfBound() throws CommandException { + if (dateSlotIndexList.get(0).getZeroBased() >= dateSlotList.size()) { + throw new CommandException(MESSAGE_OUTOFBOUND_DATESLOT_INDEX); + } + } + + /** + * Unmark the respective date slot as unassigned. + * @return updated dateSlot list + * @throws CommandException + */ + public List unmarkAssigned() throws CommandException { + if (dateSlotIndexList.isEmpty()) { + unmarkAllAssigned(); + } else { + unmarkSpecificAssigned(); + } + return dateSlotList; + } + + private void unmarkAllAssigned() throws CommandException { + for (DateSlot dateSlot : dateSlotList) { + unmarkAssignedCheck(dateSlot); + dateSlot.unmark(); + } + } + + private void unmarkSpecificAssigned() throws CommandException { + sortIndex(); + checkIndexOutOfBound(); + for (Index index : dateSlotIndexList) { + DateSlot dateSlot = dateSlotList.get(index.getZeroBased()); + unmarkAssignedCheck(dateSlot); + dateSlot.unmark(); + } + } + + /** + * Unmark the specific date slot that has the same dateTime with the dateSlot given. + * @param dateSlot + * @return updated dateSlot list + */ + public List unmarkSpecificAssignedFromHomeVisit(DateSlot dateSlot) { + DateSlot dateSlotToBeUnmarked = dateSlotList.stream().filter( + d -> d.getDateTime().equals(dateSlot.getDateTime())).findFirst().get(); + dateSlotToBeUnmarked.unmark(); + return dateSlotList; + } + + private void unmarkAssignedCheck(DateSlot dateSlot) throws CommandException { + DateSlotChecker checker = new DateSlotChecker(dateSlot); + checker.checkVisited(); + checker.checkNotAssigned(); + } + + /** + * Unmark the specified dateslot as fail visited. + * @return updated dateSlot list + * @throws CommandException + */ + public List unmarkSuccessVisited() throws CommandException { + checkIndexOutOfBound(); + DateSlot dateToBeUnmark = dateSlotList.get(dateSlotIndexList.get(0).getZeroBased()); + unmarkSuccessVisitedCheck(dateToBeUnmark); + dateToBeUnmark.markFail(); + return dateSlotList; + } + + private void unmarkSuccessVisitedCheck(DateSlot dateSlot) throws CommandException { + DateSlotChecker checker = new DateSlotChecker(dateSlot); + checker.checkNotVisited(); + } + + /** + * Undo the unmark of specified date slot as success visit. + * @return updated dateSlot list + * @throws CommandException + */ + public List undoUnmarkFailVisited() throws CommandException { + checkIndexOutOfBound(); + DateSlot dateToBeUndoUnmark = dateSlotList.get(dateSlotIndexList.get(0).getZeroBased()); + undoUnmarkFailVisitedCheck(dateToBeUndoUnmark); + dateToBeUndoUnmark.markSuccess(); + return dateSlotList; + } + + private void undoUnmarkFailVisitedCheck(DateSlot dateSlot) throws CommandException { + DateSlotChecker checker = new DateSlotChecker(dateSlot); + checker.checkNotVisitedForUndoUnmark(); + checker.checkSuccessVisited(); + } + + /** + * Remove the respective date slot from the date slot list. + * @return updated dateSlot list + * @throws CommandException + */ + public List removeDateSlot() throws CommandException { + if (dateSlotIndexList.isEmpty()) { + removeAllDateSlot(); + } else { + removeSpecificDateSlot(); + } + return dateSlotList; + } + + private void removeAllDateSlot() { + dateSlotList.clear(); + } + + private void removeSpecificDateSlot() throws CommandException { + sortIndex(); + checkIndexOutOfBound(); + for (Index index : dateSlotIndexList) { + DateSlot dateSlot = dateSlotList.get(index.getZeroBased()); + dateSlotList.remove(dateSlot); + } + } + + /** + * Add the dateSlotToBeAdded given by user to the existing date slot list. + * @param dateSlotToBeAdded + * @return updated dateSlot list + */ + public List addDateSlot(List dateSlotToBeAdded) { + dateSlotList.addAll(dateSlotToBeAdded); + return dateSlotList; + } + + /** + * Get the date slot list. + */ + public List getDateSlot() { + return this.dateSlotList; + } + + +} + + + diff --git a/src/main/java/seedu/address/logic/commands/DeassignCommand.java b/src/main/java/seedu/address/logic/commands/DeassignCommand.java new file mode 100644 index 00000000000..82c0c81fffe --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeassignCommand.java @@ -0,0 +1,118 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.category.Category; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; + +/** + * Deassigns a home-visit slot. + */ +public class DeassignCommand extends Command { + + public static final String COMMAND_WORD = "deassign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deassign home visit dateslot" + + "by using the unique id number of the patient or the nurse and respective index.\n" + + "Parameters: " + PREFIX_UID + "UID of a nurse/patient (must be a positive integer) " + + "If it is a patient, to indicate the specific dateslot to be deassigned: " + + "[" + PREFIX_DATE_AND_SLOT_INDEX + "DATE_AND_SLOT_INDEX] \n" + + "If it is a nurse, to indicate the specific homevisit to be deassigned: " + + "[" + PREFIX_DATE_AND_SLOT_INDEX + "HOME_VISIT_INDEX] \n" + + "Example: " + COMMAND_WORD + PREFIX_UID + " 1 " + + PREFIX_DATE_AND_SLOT_INDEX + "1"; + + public static final String MESSAGE_SUCCESS = "%1$s 's dateslot/homevisit has been deassigned."; + + private final Uid uid; + private final List dateslotOrHomevisitIndex; + + /** + * Creates a DeassignCommand to deassgin specific patient's date slot or + * specific nurse's home visit. + */ + public DeassignCommand(Uid uid, List dateslotOrHomevisitIndex) { + requireAllNonNull(uid, dateslotOrHomevisitIndex); + this.uid = uid; + this.dateslotOrHomevisitIndex = new ArrayList<>(); + this.dateslotOrHomevisitIndex.addAll(dateslotOrHomevisitIndex); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredPersonList(); + Optional person = lastShownList.stream().filter(p -> p.getUid().equals(uid)).findFirst(); + + if (person.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); + } + + Person personToBeDeassigned = person.get(); + if (!(personToBeDeassigned.isNurse() || personToBeDeassigned.isPatient())) { + throw new IllegalArgumentException(Category.MESSAGE_CONSTRAINTS); + } + if (personToBeDeassigned.isPatient()) { + unmarkAssignedPatient(model, personToBeDeassigned, lastShownList); + } else { + unmarkAssignedNurse(model, personToBeDeassigned, lastShownList); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, personToBeDeassigned.getUid().getUid())); + } + + private void unmarkAssignedPatient(Model model, Person person, List personList) throws CommandException { + List patientDateSlotList = ((Patient) person).getDatesSlots(); + DateSlotManager unmarker = new DateSlotManager(patientDateSlotList, dateslotOrHomevisitIndex); + List updatedDateSlotList = unmarker.unmarkAssigned(); + + InternalHomeVisitRemoverFromDateSlot homeVisitRemover = new InternalHomeVisitRemoverFromDateSlot(model, + personList, patientDateSlotList, dateslotOrHomevisitIndex); + homeVisitRemover.removeHomeVisitsForDateSlot(); + + InternalEditor editor = new InternalEditor(model); + editor.editPatient(person, updatedDateSlotList); + } + + private void unmarkAssignedNurse(Model model, Person person, List personList) throws CommandException { + List homeVisitsList = ((Nurse) person).getHomeVisits(); + List fullyScheduledList = ((Nurse) person).getFullyScheduledDates(); + HomeVisitManager remover = new HomeVisitManager(homeVisitsList, dateslotOrHomevisitIndex, fullyScheduledList); + List updatedHomeVisitList = remover.removeHomeVisits(); + List updatedFullyScheduledDatesList = remover.getFullyScheduledDateList(); + + InternalUnmarkerFromHomeVisit dateSlotUnmarker = new InternalUnmarkerFromHomeVisit(model, personList, + homeVisitsList, dateslotOrHomevisitIndex); + dateSlotUnmarker.unmarkDateSlotForHomeVisit(); + + InternalEditor editor = new InternalEditor(model); + editor.editNurse(person, updatedHomeVisitList, updatedFullyScheduledDatesList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeassignCommand // instanceof handles nulls + && uid.equals(((DeassignCommand) other).uid) + && dateslotOrHomevisitIndex.equals(((DeassignCommand) other).dateslotOrHomevisitIndex)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..a7b5ff2fdbc 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,33 +1,40 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; import java.util.List; +import java.util.Optional; import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a patient/nurse identified using it's displayed index from the + * address book. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the patient/nurse identified by the unique id number used in the displayed person list.\n" + + "Parameters: UID (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_UID + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted %1$s: %2$s"; - private final Index targetIndex; + private final Uid targetUid; - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + public DeleteCommand(Uid targetUid) { + this.targetUid = targetUid; } @Override @@ -35,19 +42,63 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); List lastShownList = model.getFilteredPersonList(); - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + Optional personToDelete = lastShownList.stream().filter(p -> p.getUid().equals(targetUid)).findFirst(); + if (!personToDelete.isPresent()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); } + Person confirmedPersonToDelete = personToDelete.get(); - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Boolean hasBeenDeleted = false; + Boolean hasBeenUnmark = false; + if (confirmedPersonToDelete.isPatient()) { + hasBeenDeleted = deleteRespectiveHomeVisit(model, confirmedPersonToDelete, lastShownList); + } else { + hasBeenUnmark = unmarkRespectiveDateSlot(model, confirmedPersonToDelete, lastShownList); + } + String extraMessage = ""; + if (hasBeenDeleted) { + extraMessage = "The respective home visit has also been deleted."; + } + if (hasBeenUnmark) { + extraMessage = "The respective date slot has also been unmarked."; + } + model.deletePerson(confirmedPersonToDelete); + return new CommandResult( + String.format("%s %s", + String.format(MESSAGE_DELETE_PERSON_SUCCESS, + confirmedPersonToDelete.getCategoryIndicator(), confirmedPersonToDelete), + extraMessage)); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + && targetUid.equals(((DeleteCommand) other).targetUid)); // state check + } + + private Boolean deleteRespectiveHomeVisit(Model model, Person person, List personList) + throws CommandException { + boolean hasDeleted = false; + List dateSlotList = ((Patient) person).getDatesSlots(); + InternalHomeVisitRemoverFromDateSlot homeVisitRemover = new InternalHomeVisitRemoverFromDateSlot(model, + personList, dateSlotList); + hasDeleted = homeVisitRemover.removeHomeVisitsForDateSlot(); + return hasDeleted; } + + private Boolean unmarkRespectiveDateSlot(Model model, Person person, List personList) + throws CommandException { + boolean hasUnmarked = false; + List homeVisitList = ((Nurse) person).getHomeVisits(); + if (!homeVisitList.isEmpty()) { + InternalUnmarkerFromHomeVisit dateSlotUnmarker = new InternalUnmarkerFromHomeVisit(model, personList, + homeVisitList); + dateSlotUnmarker.unmarkDateSlotForHomeVisit(); + hasUnmarked = true; + } + return hasUnmarked; + } + } + diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..2da6a30c1a7 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,13 +1,22 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE_INDEX; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -16,14 +25,25 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.ReverseIndexComparator; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.category.Category; import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.HomeVisit; import seedu.address.model.person.Name; +import seedu.address.model.person.NextOfKin; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Physician; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -33,35 +53,75 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the patient/nurse identified " + + "by the unique id number used in the displayed person list." + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Parameters: UID (must be a positive integer) " + + "[" + PREFIX_CATEGORY + "CATEGORY] " + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_GENDER + "GENDER] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " + + "Date and Slot are only applicable to patient and Date and Slot Index is used to indicate" + + "the specific date and slot to be edited. \n" + + "[" + PREFIX_DATE_AND_SLOT + "DATE_AND_SLOT] \n" + + "[" + PREFIX_DATE_AND_SLOT_INDEX + "DATE_AND_SLOT_INDEX] \n" + + "Unavailable Date are only applicable to nurse and Unavailable Date Index is used to indicate" + + " the specific unavailable date to be edited. \n" + + "[" + PREFIX_UNAVAILABLE_DATE + "UNAVAILABLE_DATE] \n" + + "[" + PREFIX_UNAVAILABLE_DATE_INDEX + "UNAVAILABLE_DATE_INDEX] \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_UID + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited %1$s: %2$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PERSON = "This %1$s already exists in the address book."; + public static final String MESSAGE_NURSE_INVALID_DATESLOT_EDIT = "This uid gives a nurse " + + "and there are no dates and slot (and their indexes) for nurse. " + + "Please remove the date and slot field and its index field."; + + public static final String MESSAGE_PATIENT_INVALID_UNAVAILABLE_DATE_EDIT = "This uid gives a patient " + + "and there are no unavailable dates (and their indexes) for patient. " + + "Please remove the unavailable dates field and its index field."; + + /* + * public static final String + * MESSAGE_INVALID_NUMBERS_OF_DATESLOT_AND_DATESLOTINDEX = "The dateSlot index " + * + "provided is more than the dateSlot provided." + + * "Please remove the dateSlot index or add more dateSlot."; + * + * public static final String MESSAGE_OUT_OF_BOUND_DATESLOTINDEX = + * "The dateSlot index given is out of bounds " + * + "of the existing list." + + * "Please retype another index that is within the range or left it empty."; + * + * public static final String + * MESSAGE_INVALID_NUMBERS_OF_UNAVAILABLEDATES_AND_UNAVAILABLEDATESINDEX = + * "The unavailable date index " + * + "provided is more than the unavailable date provided." + * + "Please remove the unavailable date index or add more unavailable date."; + * + * public static final String MESSAGE_OUT_OF_BOUND_UNAVAILABLEDATESINDEX = + * "The unavailable date index " + * + "given is out of bounds of the existing list." + * + "Please retype another index that is within the range or left it empty."; + */ - private final Index index; + private final Uid targetUid; private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param targetUid Uid 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); + public EditCommand(Uid targetUid, EditPersonDescriptor editPersonDescriptor) { + requireAllNonNull(targetUid, editPersonDescriptor); - this.index = index; + this.targetUid = targetUid; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } @@ -69,37 +129,148 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); List lastShownList = model.getFilteredPersonList(); + Optional personToEdit = lastShownList.stream().filter(p -> p.getUid().equals(targetUid)).findFirst(); - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (personToEdit.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person confirmedPersonToEdit = personToEdit.get(); + + checkDescriptionGiven(editPersonDescriptor, confirmedPersonToEdit); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + Person editedPerson = createEditedPerson(model, lastShownList, confirmedPersonToEdit, editPersonDescriptor); + + if (!confirmedPersonToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_PERSON, + confirmedPersonToEdit.getCategoryIndicator())); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(confirmedPersonToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, + confirmedPersonToEdit.getCategoryIndicator(), editedPerson)); + + } + + private void checkDescriptionGiven(EditPersonDescriptor editPersonDescriptor, Person personToEdit) + throws CommandException { + boolean hasDatesSlots = editPersonDescriptor.getDatesSlots().isPresent(); + boolean hasDateSlotIndexes = editPersonDescriptor.getDateSlotIndexes().isPresent(); + boolean hasUnavailableDates = editPersonDescriptor.getUnavailableDates().isPresent(); + boolean hasUnavailableDateIndexes = editPersonDescriptor.getDateIndexes().isPresent(); + boolean isNurse = editPersonDescriptor.getCategory().equals("N") || personToEdit instanceof Nurse; + + if (isNurse && (hasDateSlotIndexes || hasDatesSlots)) { + throw new CommandException(MESSAGE_NURSE_INVALID_DATESLOT_EDIT); + } + if (hasUnavailableDates || hasUnavailableDateIndexes) { + throw new CommandException(MESSAGE_PATIENT_INVALID_UNAVAILABLE_DATE_EDIT); + } } /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + public Person createEditedPerson(Model model, List personList, Person personToEdit, + EditPersonDescriptor editPersonDescriptor) throws CommandException { assert personToEdit != null; - + Category updatedCategory = editPersonDescriptor.getCategory().orElse(personToEdit.getCategory()); + Uid uid = editPersonDescriptor.getUid().orElse(personToEdit.getUid()); Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); 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); + if (personToEdit instanceof Patient && updatedCategory.isPatient()) { + return createUpdatedPatient(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, + updatedTags, editPersonDescriptor, personToEdit, model, personList); + + } else if (updatedCategory.isPatient()) { + return createNewPatient(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, + updatedTags, editPersonDescriptor); + + } else if (personToEdit instanceof Nurse && updatedCategory.isNurse()) { + return createUpdatedNurse(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, + updatedTags, editPersonDescriptor, personToEdit, model, personList); + + } else if (updatedCategory.isNurse()) { + return createNewNurse(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, + updatedTags, editPersonDescriptor); + } + throw new IllegalArgumentException(Category.MESSAGE_CONSTRAINTS); + } + + private Patient createUpdatedPatient(Uid uid, Name updatedName, Gender updatedGender, Phone updatedPhone, + Email updatedEmail, Address updatedAddress, Set updatedTags, + EditPersonDescriptor editPersonDescriptor, Person personToEdit, + Model model, List personList) throws CommandException { + + Optional updatedPhysician = editPersonDescriptor.getPhysician() + .orElse(((Patient) personToEdit).getAttendingPhysician()); + Optional updatedNextOfKin = editPersonDescriptor.getNextOfKin() + .orElse(((Patient) personToEdit).getNextOfKin()); + + List originalDateSlot = ((Patient) personToEdit).getDatesSlots(); + Optional> toBeUpdateDateSlot = editPersonDescriptor.getDatesSlots(); + Optional> toBeUpdateDateSlotIndexes = editPersonDescriptor.getDateSlotIndexes(); + EditedDateSlotCreator creator = new EditedDateSlotCreator(model, personList, originalDateSlot, + toBeUpdateDateSlot, toBeUpdateDateSlotIndexes); + List updatedDateSlot = creator.createEditedDateSlotList(); + + return new Patient(uid, updatedName, updatedGender, updatedPhone, updatedEmail, + updatedAddress, updatedTags, updatedDateSlot, updatedPhysician, updatedNextOfKin); + } + + private Patient createNewPatient(Uid uid, Name updatedName, Gender updatedGender, Phone updatedPhone, + Email updatedEmail, Address updatedAddress, Set updatedTags, + EditPersonDescriptor editPersonDescriptor) { + + Optional updatedPhysician = editPersonDescriptor.getPhysician() + .orElse(Optional.empty()); + Optional updatedNextOfKin = editPersonDescriptor.getNextOfKin() + .orElse(Optional.empty()); + List updatedDateSlot = editPersonDescriptor.getDatesSlots().orElse(null); + + return new Patient(uid, updatedName, updatedGender, updatedPhone, updatedEmail, + updatedAddress, updatedTags, updatedDateSlot, updatedPhysician, updatedNextOfKin); + } + + private Nurse createUpdatedNurse(Uid uid, Name updatedName, Gender updatedGender, Phone updatedPhone, + Email updatedEmail, Address updatedAddress, Set updatedTags, + EditPersonDescriptor editPersonDescriptor, Person personToEdit, + Model model, List personList) throws CommandException { + + List originalDate = ((Nurse) personToEdit).getUnavailableDates(); + Optional> toBeUpdateDate = editPersonDescriptor.getUnavailableDates(); + Optional> toBeUpdateDateIndexes = editPersonDescriptor.getDateIndexes(); + EditedUnavailableDateCreator creator = new EditedUnavailableDateCreator(model, personToEdit, personList, + originalDate, toBeUpdateDate, toBeUpdateDateIndexes); + List updatedUnavailableDate = creator.createEditedUnavailableDateList(editPersonDescriptor); + + List updatedFullyScheduledDateList = editPersonDescriptor.getFullyScheduledDates() + .orElse(((Nurse) personToEdit).getFullyScheduledDates()); + List updatedHomeVisitList = editPersonDescriptor.getHomeVisits() + .orElse(((Nurse) personToEdit).getHomeVisits()); + + return new Nurse(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, updatedTags, + updatedUnavailableDate, updatedHomeVisitList, updatedFullyScheduledDateList); + } + + private Nurse createNewNurse(Uid uid, Name updatedName, Gender updatedGender, Phone updatedPhone, + Email updatedEmail, Address updatedAddress, Set updatedTags, + EditPersonDescriptor editPersonDescriptor) { + + List updatedUnavailableDate = editPersonDescriptor.getUnavailableDates().orElse(null); + List updatedHomeVisitList = editPersonDescriptor.getHomeVisits().orElse(null); + List updatedFullyScheduledDateList = editPersonDescriptor.getFullyScheduledDates() + .orElse(null); + + return new Nurse(uid, updatedName, updatedGender, updatedPhone, updatedEmail, updatedAddress, updatedTags, + updatedUnavailableDate, updatedHomeVisitList, updatedFullyScheduledDateList); } @Override @@ -116,74 +287,140 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; - return index.equals(e.index) + return targetUid.equals(e.targetUid) && editPersonDescriptor.equals(e.editPersonDescriptor); } /** - * 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 { + private Category category; + private Uid uid; private Name name; + private Gender gender; private Phone phone; private Email email; private Address address; private Set tags; - - public EditPersonDescriptor() {} + private List datesSlots; + private List dateSlotIndexes; + private List homeVisits; + private List unavailableDates; + private List dateIndexes; + private List fullyScheduledDates; + private Optional physician; + private Optional nextOfKin; + + public EditPersonDescriptor() { + } /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { + setCategory(toCopy.category); + setUid(toCopy.uid); setName(toCopy.name); + setGender(toCopy.gender); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); setTags(toCopy.tags); + setDatesSlots(toCopy.datesSlots); + setDateSlotIndexes(toCopy.dateSlotIndexes); + setHomeVisits(toCopy.homeVisits); + setUnavailableDates(toCopy.unavailableDates); + setDateIndexes(toCopy.dateIndexes); + setFullyScheduledDates(toCopy.fullyScheduledDates); + setPhysician(toCopy.physician); + setNextOfKin(toCopy.nextOfKin); + } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, gender, phone, email, address, + tags, datesSlots, dateSlotIndexes, unavailableDates, dateIndexes); } - public void setName(Name name) { - this.name = name; + public Optional getCategory() { + return Optional.ofNullable(category); + } + + public void setCategory(Category category) { + this.category = category; + } + + /** + * @return the id + */ + public Optional getUid() { + return Optional.ofNullable(uid); + } + + /** + * @param uid the id to set + */ + public void setUid(Uid uid) { + this.uid = uid; } public Optional getName() { return Optional.ofNullable(name); } - public void setPhone(Phone phone) { - this.phone = phone; + public void setName(Name name) { + this.name = name; + } + + public Optional getGender() { + return Optional.ofNullable(gender); + } + + public void setGender(Gender gender) { + this.gender = gender; } public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + public void setPhone(Phone phone) { + this.phone = phone; } public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setEmail(Email email) { + this.email = email; } public Optional
getAddress() { return Optional.ofNullable(address); } + public void setAddress(Address address) { + this.address = address; + } + + /** + * Returns an unmodifiable tag set, which throws + * {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -193,12 +430,132 @@ public void setTags(Set tags) { } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns a dateSlot list + * Returns {@code Optional#empty()} if {@code dateSlots} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional> getDatesSlots() { + return (datesSlots != null) ? Optional.of(new ArrayList(datesSlots)) : Optional.empty(); + } + + /** + * Sets {@code datesSlots} to this object's {@code datesSlots}. + */ + public void setDatesSlots(List datesSlots) { + this.datesSlots = (datesSlots != null) ? new ArrayList(datesSlots) : null; + } + + /** + * Returns a dateSlotIndexes list + * Returns {@code Optional#empty()} if {@code dateSlotIndexes} is null. + */ + public Optional> getDateSlotIndexes() { + return (dateSlotIndexes != null) ? Optional.of(new ArrayList(dateSlotIndexes)) : Optional.empty(); + } + + /** + * Sets {@code dateSlotIndexes} to this object's {@code dateSlotIndexes}. + */ + public void setDateSlotIndexes(List dateSlotIndexes) { + this.dateSlotIndexes = (dateSlotIndexes != null) ? new ArrayList(dateSlotIndexes) : null; + } + + /** + * Returns a homeVisit list + * Returns {@code Optional#empty()} if {@code homeVisits} is null. + */ + public Optional> getHomeVisits() { + return (homeVisits != null) ? Optional.of(new ArrayList(homeVisits)) : Optional.empty(); + } + + /** + * Sets {@code homeVisits} to this object's {@code homeVisits}. + */ + public void setHomeVisits(List homeVisits) { + this.homeVisits = (homeVisits != null) ? new ArrayList(homeVisits) : null; + } + + /** + * Returns a unavailableDate list + * Returns {@code Optional#empty()} if {@code unavailableDate} is null. + */ + public Optional> getUnavailableDates() { + return (unavailableDates != null) ? Optional.of(new ArrayList(unavailableDates)) : Optional.empty(); + } + + /** + * Sets {@code unavailableDate} to this object's {@code unavailableDates}. + */ + public void setUnavailableDates(List unavailableDates) { + this.unavailableDates = (unavailableDates != null) ? new ArrayList(unavailableDates) : null; + } + + /** + * Returns a dateIndexes list + * Returns {@code Optional#empty()} if {@code dateIndexes} is null. + */ + public Optional> getDateIndexes() { + return (dateIndexes != null) ? Optional.of(new ArrayList(dateIndexes)) : Optional.empty(); + } + + /** + * Sets {@code dateIndexes} to this object's {@code dateIndexes}. + */ + public void setDateIndexes(List dateIndexes) { + this.dateIndexes = (dateIndexes != null) ? new ArrayList(dateIndexes) : null; + } + + /** + * Returns a fullyScheduledlist + * Returns {@code Optional#empty()} if {@code fullyScheduledDates} is null. + */ + public Optional> getFullyScheduledDates() { + return (fullyScheduledDates != null) ? Optional.of(new ArrayList(fullyScheduledDates)) + : Optional.empty(); + } + + /** + * Sets {@code fullyScheduledDates} to this object's {@code fullScheduledDates}. + */ + public void setFullyScheduledDates(List fullyScheduledDates) { + this.fullyScheduledDates = (fullyScheduledDates != null) ? new ArrayList(fullyScheduledDates) : null; + } + + /** + * Returns the attending physician + * + * @return {@code Optional#empty()} if {@code physician} is null. + */ + public Optional> getPhysician() { + return (physician != null) ? Optional.of(physician) : Optional.empty(); + } + + /** + * Sets {@code p} to this object's {@code physician}. + * + * @return + */ + public EditPersonDescriptor setPhysician(Optional p) { + physician = p; + return this; + } + + /** + * Returns the next of kin + * + * @return {@code Optioanl.empty()} if {@code nextOfKin} is null. + */ + public Optional> getNextOfKin() { + return (nextOfKin != null) ? Optional.of(nextOfKin) : Optional.empty(); + } + + /** + * Sets {@code n} to this object's {@code nextOfKin} + * + * @return + */ + public EditPersonDescriptor setNextOfKin(Optional n) { + nextOfKin = n; + return this; } @Override @@ -216,11 +573,339 @@ public boolean equals(Object other) { // state check EditPersonDescriptor e = (EditPersonDescriptor) other; - return getName().equals(e.getName()) + return getUid().equals(e.getUid()) + && getName().equals(e.getName()) + && getCategory().equals(e.getCategory()) + && getGender().equals(e.getGender()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getDatesSlots().equals(e.getDatesSlots()) + && getDateSlotIndexes().equals(e.getDateSlotIndexes()) + && getTags().equals(e.getTags()) + && getHomeVisits().equals(e.getHomeVisits()) + && getUnavailableDates().equals(e.getUnavailableDates()) + && getDateIndexes().equals(e.getDateIndexes()) + && getFullyScheduledDates().equals(e.getFullyScheduledDates()); } } + + /** + * To create the edited date slot list + * If delete date slot occur, the respective home visit will also be deleted + */ + public static class EditedDateSlotCreator { + + public static final String MESSAGE_INVALID_NUMBERS_OF_DATESLOT_AND_DATESLOTINDEX = "The dateSlot index " + + "provided is more than the dateSlot provided. " + + "Please remove the dateSlot index or add more dateSlot."; + private final Model model; + private final List personList; + private final List originalDateSlotList; + private final List toBeUpdateDateSlots; + private final List toBeUpdateDateSlotsIndexes; + private final Boolean isDateSlotsGivenNull; + private final Boolean isDateSlotIndexesGivenNull; + private final Boolean isDateSlotsGivenEmpty; + private final Boolean isDateSlotIndexesGivenEmpty; + + EditedDateSlotCreator(Model model, List personList, List originalDateSlots, + Optional> toBeUpdateDateSlots, + Optional> toBeUpdateDateSlotsIndexes) { + this.model = model; + this.personList = personList; + this.originalDateSlotList = originalDateSlots; + this.isDateSlotsGivenNull = getIsDateSlotsGivenNull(toBeUpdateDateSlots); + this.isDateSlotIndexesGivenNull = getIsDateSlotIndexesGivenNull(toBeUpdateDateSlotsIndexes); + this.toBeUpdateDateSlots = createToBeUpdateDateSlotList(toBeUpdateDateSlots); + this.toBeUpdateDateSlotsIndexes = createToBeUpdatedIndexList(toBeUpdateDateSlotsIndexes); + this.isDateSlotsGivenEmpty = getIsDateSlotsGivenEmpty(); + this.isDateSlotIndexesGivenEmpty = getIsDateSlotIndexesGivenEmpty(); + } + + private boolean getIsDateSlotsGivenNull(Optional> toBeUpdateDateSlots) { + return toBeUpdateDateSlots.equals(Optional.empty()); + } + + private boolean getIsDateSlotIndexesGivenNull(Optional> toBeUpdateDateSlotIndexes) { + return toBeUpdateDateSlotIndexes.equals(Optional.empty()); + } + + private List createToBeUpdateDateSlotList(Optional> toBeUpdateDateSlots) { + List toBeUpdateDateSlotList = new ArrayList<>(); + if (!isDateSlotsGivenNull) { + toBeUpdateDateSlotList = new ArrayList<>(toBeUpdateDateSlots.get()); + } + return toBeUpdateDateSlotList; + } + + private List createToBeUpdatedIndexList(Optional> toBeUpdateDateSlotsIndexes) { + List toBeUpdateDateSlotIndexList = new ArrayList<>(); + if (!isDateSlotIndexesGivenNull) { + toBeUpdateDateSlotIndexList = new ArrayList<>(toBeUpdateDateSlotsIndexes.get()); + } + return toBeUpdateDateSlotIndexList; + } + + private boolean getIsDateSlotsGivenEmpty() { + return !isDateSlotsGivenNull && toBeUpdateDateSlots.isEmpty(); + } + + private boolean getIsDateSlotIndexesGivenEmpty() { + return !isDateSlotIndexesGivenNull && toBeUpdateDateSlotsIndexes.isEmpty(); + } + + private List createEditedDateSlotList() throws CommandException { + + if (isDelete()) { + // Deletes all the dateTime in the existing list/ Deletes specific dateTime in + // the existing list + return removeActionForDateSlot(); + + } else if (isAdd()) { + // Add the given dateTime to the existing list + return addActionForDateSlot(); + + } else if (isEdit()) { + // Remove specific dateSlot in the existing list and add the given dateSlot + return editActionForDateSlot(); + + } + // Remain as original dateTime list, no changes made + return originalDateSlotList; + } + + private Boolean isDelete() { + Boolean isAllDeleted = (isDateSlotsGivenNull && isDateSlotIndexesGivenEmpty) + || (isDateSlotIndexesGivenNull && isDateSlotsGivenEmpty) + || (isDateSlotsGivenEmpty && isDateSlotIndexesGivenEmpty); + Boolean isSpecificDeleted = !isDateSlotIndexesGivenEmpty && (isDateSlotsGivenNull || isDateSlotsGivenEmpty) + && !isDateSlotIndexesGivenNull; + return isAllDeleted || isSpecificDeleted; + } + + private Boolean isAdd() { + return !isDateSlotsGivenEmpty && (isDateSlotIndexesGivenNull || isDateSlotIndexesGivenEmpty); + } + + private Boolean isEdit() { + return !isDateSlotsGivenEmpty && !isDateSlotIndexesGivenEmpty; + } + + private List removeActionForDateSlot() throws CommandException { + DateSlotManager remover = new DateSlotManager(originalDateSlotList, toBeUpdateDateSlotsIndexes); + if (!toBeUpdateDateSlotsIndexes.isEmpty()) { + remover.checkIndexOutOfBound(); + } + InternalHomeVisitRemoverFromDateSlot homeVisitRemover = new InternalHomeVisitRemoverFromDateSlot(model, + personList, originalDateSlotList, toBeUpdateDateSlotsIndexes); + homeVisitRemover.removeHomeVisitsForDateSlot(); + return remover.removeDateSlot(); + } + + private List addActionForDateSlot() { + DateSlotManager adder = new DateSlotManager(originalDateSlotList); + return adder.addDateSlot(toBeUpdateDateSlots); + } + + private List editActionForDateSlot() throws CommandException { + checkDateSlotAndIndex(); + DateSlotManager editor = new DateSlotManager(originalDateSlotList, toBeUpdateDateSlotsIndexes); + if (!toBeUpdateDateSlotsIndexes.isEmpty()) { + editor.checkIndexOutOfBound(); + } + InternalHomeVisitRemoverFromDateSlot homeVisitRemover = new InternalHomeVisitRemoverFromDateSlot(model, + personList, originalDateSlotList, toBeUpdateDateSlotsIndexes); + homeVisitRemover.removeHomeVisitsForDateSlot(); + editor.removeDateSlot(); + return editor.addDateSlot(toBeUpdateDateSlots); + } + + private void checkDateSlotAndIndex() throws CommandException { + if (toBeUpdateDateSlots.size() < toBeUpdateDateSlotsIndexes.size()) { + throw new CommandException(MESSAGE_INVALID_NUMBERS_OF_DATESLOT_AND_DATESLOTINDEX); + } + } + } + + /** + * To create the edited unavailable date list. + * If adding a new unavailable date occurs, then the respective home visit will + * be checked and removed if clash + */ + public static class EditedUnavailableDateCreator { + + public static final String MESSAGE_OUT_OF_BOUND_UNAVAILABLE_DATE_INDEX = "The unavailable date index " + + "given is out of bounds of the existing list." + + " Please retype another index that is within the range or left it empty."; + + public static final String MESSAGE_INVALID_NUMBERS_OF_UNAVAILABLE_DATES_AND_UNAVAILABLE_DATE_INDEXES = + "The unavailable date index provided is more than the unavailable date provided." + + " Please remove the unavailable date index or add more unavailable date."; + + private final Model model; + private final Person nurseToEdit; + private final List personList; + private final List originalUnavailableDateList; + private final List toBeUpdateUnavailableDates; + private final List toBeUpdateUnavailableDateIndexes; + private final Boolean isUnavailableDatesGivenNull; + private final Boolean isUnavailableDateIndexesGivenNull; + private final Boolean isUnavailableDatesGivenEmpty; + private final Boolean isUnavailableDateIndexesGivenEmpty; + + EditedUnavailableDateCreator(Model model, Person nurseToEdit, List personList, + List originalUnavailableDates, + Optional> toBeUpdateUnavailableDates, + Optional> toBeUpdateUnavailableDateIndexes) { + this.model = model; + this.nurseToEdit = nurseToEdit; + this.personList = personList; + this.originalUnavailableDateList = originalUnavailableDates; + this.isUnavailableDatesGivenNull = getIsUnavailableDatesGivenNull(toBeUpdateUnavailableDates); + this.isUnavailableDateIndexesGivenNull = getIsUnavailableDateIndexesGivenNull( + toBeUpdateUnavailableDateIndexes); + this.toBeUpdateUnavailableDates = createToBeUpdateUnavailableDateList(toBeUpdateUnavailableDates); + this.toBeUpdateUnavailableDateIndexes = createToBeUpdatedIndexList(toBeUpdateUnavailableDateIndexes); + this.isUnavailableDatesGivenEmpty = getIsUnavailableDatesGivenEmpty(); + this.isUnavailableDateIndexesGivenEmpty = getIsUnavailableDateIndexesGivenEmpty(); + } + + private boolean getIsUnavailableDatesGivenNull(Optional> toBeUpdateUnavailableDates) { + return toBeUpdateUnavailableDates.equals(Optional.empty()); + } + + private boolean getIsUnavailableDateIndexesGivenNull(Optional> toBeUpdateUnavailableDateIndexes) { + return toBeUpdateUnavailableDateIndexes.equals(Optional.empty()); + } + + private List createToBeUpdateUnavailableDateList(Optional> toBeUpdateUnavailableDates) { + List toBeUpdateUnavailableDateList = new ArrayList<>(); + if (!isUnavailableDatesGivenNull) { + toBeUpdateUnavailableDateList = new ArrayList<>(toBeUpdateUnavailableDates.get()); + } + return toBeUpdateUnavailableDateList; + } + + private List createToBeUpdatedIndexList(Optional> toBeUpdateUnavailableDateIndexes) { + List toBeUpdateUnavailableDateIndexList = new ArrayList<>(); + if (!isUnavailableDateIndexesGivenNull) { + toBeUpdateUnavailableDateIndexList = new ArrayList<>(toBeUpdateUnavailableDateIndexes.get()); + } + return toBeUpdateUnavailableDateIndexList; + } + + private boolean getIsUnavailableDatesGivenEmpty() { + return !isUnavailableDatesGivenNull && toBeUpdateUnavailableDates.isEmpty(); + } + + private boolean getIsUnavailableDateIndexesGivenEmpty() { + return !isUnavailableDateIndexesGivenNull && toBeUpdateUnavailableDateIndexes.isEmpty(); + } + + private List createEditedUnavailableDateList(EditPersonDescriptor editPersonDescriptor) + throws CommandException { + + if (isAllDelete()) { + // Deletes all the unavailable dates in the existing list + return removeAllActionForUnavailableDate(); + + } else if (isSpecificDelete()) { + // Deletes specific unavailable dates in the existing list + return removeSpecificActionForUnavailableDate(); + + } else if (isAdd()) { + // Add the given unavailable dates to the existing list + return addActionForUnavailableDate(editPersonDescriptor); + + } else if (isEdit()) { + // Remove specific unavailable dates in the existing list and add the given + // unavailable dates + return editActionForUnavailableDates(editPersonDescriptor); + + } else { + // Remain as original unavailable dates list, no changes made + return originalUnavailableDateList; + } + } + + private Boolean isAllDelete() { + return (isUnavailableDatesGivenNull && isUnavailableDateIndexesGivenEmpty) + || (isUnavailableDateIndexesGivenNull && isUnavailableDatesGivenEmpty) + || (isUnavailableDatesGivenEmpty && isUnavailableDateIndexesGivenEmpty); + } + + private Boolean isSpecificDelete() { + return !isUnavailableDateIndexesGivenEmpty && (isUnavailableDatesGivenNull + || isUnavailableDatesGivenEmpty) && !isUnavailableDateIndexesGivenNull; + } + + private Boolean isAdd() { + return !isUnavailableDatesGivenEmpty && (isUnavailableDateIndexesGivenNull + || isUnavailableDateIndexesGivenEmpty); + } + + private Boolean isEdit() { + return !isUnavailableDatesGivenEmpty && !isUnavailableDateIndexesGivenEmpty; + } + + private List removeAllActionForUnavailableDate() throws CommandException { + return new ArrayList<>(); + } + + private List removeSpecificActionForUnavailableDate() throws CommandException { + sortIndex(); + checkIndexOutOfBound(); + for (Index index : toBeUpdateUnavailableDateIndexes) { + Date unavailableDate = originalUnavailableDateList.get(index.getZeroBased()); + originalUnavailableDateList.remove(unavailableDate); + } + return originalUnavailableDateList; + } + + private void sortIndex() { + ReverseIndexComparator comp = new ReverseIndexComparator(); + this.toBeUpdateUnavailableDateIndexes.sort(comp); + } + + private void checkIndexOutOfBound() throws CommandException { + if (toBeUpdateUnavailableDateIndexes.get(0).getZeroBased() >= originalUnavailableDateList.size()) { + throw new CommandException(MESSAGE_OUT_OF_BOUND_UNAVAILABLE_DATE_INDEX); + } + } + + private List addActionForUnavailableDate(EditPersonDescriptor editPersonDescriptor) { + List homeVisitList = ((Nurse) nurseToEdit).getHomeVisits(); + List fullyScheduledDateList = ((Nurse) nurseToEdit).getFullyScheduledDates(); + InternalUnmarkerFromHomeVisit unmarker = new InternalUnmarkerFromHomeVisit(model, + personList, homeVisitList); + unmarker.unmarkDateSlotForUnavailableDates(toBeUpdateUnavailableDates); + + HomeVisitManager remover = new HomeVisitManager(homeVisitList, fullyScheduledDateList); + List updatedHomeVisitList = remover.removeHomeVisitFromUnavailableDates( + toBeUpdateUnavailableDates); + List updatedFullyScheduledDateList = remover.getFullyScheduledDateList(); + editPersonDescriptor.setHomeVisits(updatedHomeVisitList); + editPersonDescriptor.setFullyScheduledDates(updatedFullyScheduledDateList); + + originalUnavailableDateList.addAll(toBeUpdateUnavailableDates); + return originalUnavailableDateList; + } + + private List editActionForUnavailableDates(EditPersonDescriptor editPersonDescriptor) + throws CommandException { + checkDateSlotAndIndex(); + removeSpecificActionForUnavailableDate(); + addActionForUnavailableDate(editPersonDescriptor); + return originalUnavailableDateList; + } + + private void checkDateSlotAndIndex() throws CommandException { + if (toBeUpdateUnavailableDates.size() < toBeUpdateUnavailableDateIndexes.size()) { + throw new CommandException(MESSAGE_INVALID_NUMBERS_OF_UNAVAILABLE_DATES_AND_UNAVAILABLE_DATE_INDEXES); + } + } + + } + } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..fbc67241b46 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Thank you for using Healthcare Xpress!"; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..84fcd8fe991 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -7,14 +7,15 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in address book whose name contains any of the + * argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all patients and nurses names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; @@ -37,6 +38,6 @@ public CommandResult execute(Model model) { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check + && predicate.equals(((FindCommand) other).predicate)); // state check } } diff --git a/src/main/java/seedu/address/logic/commands/HomeVisitManager.java b/src/main/java/seedu/address/logic/commands/HomeVisitManager.java new file mode 100644 index 00000000000..3c523dbc3a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/HomeVisitManager.java @@ -0,0 +1,223 @@ +package seedu.address.logic.commands; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.ReverseIndexComparator; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; + +/** + * A class to manage all home visit related task. + */ +public class HomeVisitManager { + + public static final String MESSAGE_OUTOFBOUND_HOMEVISIT_INDEX = "The home visit index given is out of bounds."; + private static final int NUMBER_OF_SLOT_PER_DAY = 4; + public final List homeVisitList; + public final List homeVisitIndex; + public final List fullyScheduledDateList; + + /** + * Construct a HomeVisitManager. + * @param homeVisitList + * @param homeVisitIndex + * @param fullyScheduledDateList + */ + public HomeVisitManager(List homeVisitList, List homeVisitIndex, + List fullyScheduledDateList) { + this.homeVisitList = new ArrayList<>(); + for (HomeVisit homeVisit : homeVisitList) { + this.homeVisitList.add(homeVisit.clone()); + } + this.homeVisitIndex = new ArrayList<>(homeVisitIndex); + this.fullyScheduledDateList = new ArrayList<>(fullyScheduledDateList); + } + + /** + * Construct a HomeVisitManager with empty home visit index list. + * @param homeVisitList + * @param fullyScheduledDateList + */ + public HomeVisitManager(List homeVisitList, List fullyScheduledDateList) { + this.homeVisitList = new ArrayList<>(); + for (HomeVisit homeVisit : homeVisitList) { + this.homeVisitList.add(homeVisit.clone()); + } + this.homeVisitIndex = new ArrayList<>(); + this.fullyScheduledDateList = new ArrayList<>(fullyScheduledDateList); + } + + /** + * Create a homevisit list for a nurse. + * @param dateSlotList + * @param dateSlotIndexList + * @param patientUidNo + * @return homevisit list + */ + public List createHomeVisitList(List dateSlotList, + List dateSlotIndexList, Long patientUidNo) { + if (dateSlotIndexList.isEmpty()) { + createAllHomeVisit(dateSlotList, patientUidNo); + } else { + createSpecificHomeVisit(dateSlotList, dateSlotIndexList, patientUidNo); + } + return homeVisitList; + } + + private void createAllHomeVisit(List dateSlotList, Long patientUidNo) { + for (DateSlot dateSlot : dateSlotList) { + createHomeVisit(dateSlot, patientUidNo); + } + } + + private void createSpecificHomeVisit(List dateSlotList, List dateSlotIndexList, + Long patientUidNo) { + List sortedDateSlotIndexList = sortIndex(dateSlotIndexList); + for (Index index : sortedDateSlotIndexList) { + DateSlot dateSlot = dateSlotList.get(index.getZeroBased()); + createHomeVisit(dateSlot, patientUidNo); + } + } + + private void createHomeVisit(DateSlot dateSlot, Long patientUidNo) { + HomeVisit homeVisit = new HomeVisit(dateSlot, patientUidNo); + homeVisitList.add(homeVisit); + LocalDate date = dateSlot.getDate(); + addFullyScheduledDate(date); + } + + private void addFullyScheduledDate(LocalDate date) { + int frequencyCount = 0; + for (HomeVisit homevisit : homeVisitList) { + LocalDate dateToCheck = homevisit.getDateSlot().getDate(); + if (dateToCheck.equals(date)) { + frequencyCount++; + } + } + if (frequencyCount == NUMBER_OF_SLOT_PER_DAY) { + fullyScheduledDateList.add(new Date(date)); + } + } + + private List sortIndex(List indexList) { + ReverseIndexComparator comp = new ReverseIndexComparator(); + indexList.sort(comp); + return indexList; + } + + /** + * Remove homevisit from a nurse's homevisit list. + * @return updated homevisit list + * @throws CommandException + */ + public List removeHomeVisits() throws CommandException { + if (homeVisitIndex.isEmpty()) { + removeAllHomeVisit(); + } else { + removeSpecificHomeVisit(); + } + return homeVisitList; + } + + private void removeAllHomeVisit() throws CommandException { + for (HomeVisit homeVisit : homeVisitList) { + LocalDate date = homeVisit.getDateSlot().getDate(); + removeFullyScheduledDate(date); + removeCheck(homeVisit); + } + homeVisitList.clear(); + } + + private void removeSpecificHomeVisit() throws CommandException { + List sortedHomeVisitIndexList = sortIndex(homeVisitIndex); + checkIndexOutOfBound(); + for (Index index : sortedHomeVisitIndexList) { + HomeVisit homeVisit = homeVisitList.get(index.getZeroBased()); + removeCheck(homeVisit); + homeVisitList.remove(homeVisit); + LocalDate date = homeVisit.getDateSlot().getDate(); + removeFullyScheduledDate(date); + } + } + + private void removeCheck(HomeVisit homeVisit) throws CommandException { + DateSlot dateSlot = homeVisit.getDateSlot(); + DateSlotChecker checker = new DateSlotChecker(dateSlot); + checker.checkVisited(); + } + + private void checkIndexOutOfBound() throws CommandException { + if (homeVisitIndex.get(0).getZeroBased() >= homeVisitList.size()) { + throw new CommandException(MESSAGE_OUTOFBOUND_HOMEVISIT_INDEX); + } + } + + /** + * Remove corresponding home visit of the given dateSlot from the homevisit list. + * @param dateSlot + * @return updated home visit list + */ + public List removeHomeVisitFromDateSlot(DateSlot dateSlot) { + HomeVisit homeVisitToBeDeleted = homeVisitList.stream().filter( + h -> h.getDateSlot().getDateTime().equals(dateSlot.getDateTime())).findFirst().get(); + + homeVisitList.remove(homeVisitToBeDeleted); + LocalDate date = dateSlot.getDate(); + removeFullyScheduledDate(date); + return this.homeVisitList; + } + + private void removeFullyScheduledDate(LocalDate date) { + Optional dateToBeDeleted = fullyScheduledDateList.stream().filter( + h -> h.getDate().equals(date)).findFirst(); + + if (!dateToBeDeleted.isEmpty()) { + fullyScheduledDateList.remove(dateToBeDeleted.get()); + } + } + + /** + * To get the fully scheduled date list. + * @return fullyScheduledDateList + */ + public List getFullyScheduledDateList() { + return this.fullyScheduledDateList; + } + + /** + * Remove corresponding home visit that have time clash with the unavailable date. + * @param unavailableDateList + * @return updated home visit list + */ + public List removeHomeVisitFromUnavailableDates(List unavailableDateList) { + for (Date date : unavailableDateList) { + removeHomeVisitFromUnavailableDate(date); + } + return homeVisitList; + } + + private void removeHomeVisitFromUnavailableDate(Date unavailableDate) { + List toBeUpdatedHomeVisitList = new ArrayList<>(homeVisitList); + for (HomeVisit homeVisit : homeVisitList) { + Boolean isSameDate = checkSameDate(homeVisit, unavailableDate); + if (isSameDate) { + toBeUpdatedHomeVisitList.remove(homeVisit); + } + } + homeVisitList.clear(); + homeVisitList.addAll(toBeUpdatedHomeVisitList); + removeFullyScheduledDate(unavailableDate.getDate()); + } + + private Boolean checkSameDate(HomeVisit homeVisit, Date unavailableDate) { + return homeVisit.getDateSlot().getDate().equals(unavailableDate.getDate()); + } + +} + diff --git a/src/main/java/seedu/address/logic/commands/InternalEditor.java b/src/main/java/seedu/address/logic/commands/InternalEditor.java new file mode 100644 index 00000000000..774e1c6ff34 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/InternalEditor.java @@ -0,0 +1,81 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.model.Model; +import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Name; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; +import seedu.address.model.tag.Tag; + +/** + * A class to internally edit a patient / nurse. + */ +public class InternalEditor { + + public final Model model; + + public InternalEditor(Model model) { + this.model = model; + } + + /** + * Edit the patient to update the dateSlotList. + * @param patient + * @param dateSlotList + */ + public void editPatient(Person patient, List dateSlotList) { + Uid patientUid = patient.getUid(); + List lastShownList = model.getFilteredPersonList(); + Optional personToEdit = lastShownList.stream().filter(p -> p.getUid().equals(patientUid)).findFirst(); + Person confirmedPersonToEdit = personToEdit.get(); + Uid uid = confirmedPersonToEdit.getUid(); + Name name = confirmedPersonToEdit.getName(); + Gender gender = confirmedPersonToEdit.getGender(); + Phone phone = confirmedPersonToEdit.getPhone(); + Email email = confirmedPersonToEdit.getEmail(); + Address address = confirmedPersonToEdit.getAddress(); + Set tags = confirmedPersonToEdit.getTags(); + Person newPerson = new Patient(uid, name, gender, phone, email, address, tags, dateSlotList); + model.setPerson(confirmedPersonToEdit, newPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + /** + * Edit the nurse to update homeVisitList and fullyScheduledDateList. + * @param nurse + * @param homeVisitList + * @param fullyScheduledDateList + */ + public void editNurse(Person nurse, List homeVisitList, List fullyScheduledDateList) { + Uid nurseUid = nurse.getUid(); + List lastShownList = model.getFilteredPersonList(); + Optional personToEdit = lastShownList.stream().filter(p -> p.getUid().equals(nurseUid)).findFirst(); + Person confirmedPersonToEdit = personToEdit.get(); + Uid uid = confirmedPersonToEdit.getUid(); + Name name = confirmedPersonToEdit.getName(); + Gender gender = confirmedPersonToEdit.getGender(); + Phone phone = confirmedPersonToEdit.getPhone(); + Email email = confirmedPersonToEdit.getEmail(); + Address address = confirmedPersonToEdit.getAddress(); + Set tags = confirmedPersonToEdit.getTags(); + List unavailableDates = ((Nurse) confirmedPersonToEdit).getUnavailableDates(); + Person newPerson = new Nurse(uid, name, gender, phone, email, address, tags, unavailableDates, + homeVisitList, fullyScheduledDateList); + model.setPerson(confirmedPersonToEdit, newPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/InternalHomeVisitRemoverFromDateSlot.java b/src/main/java/seedu/address/logic/commands/InternalHomeVisitRemoverFromDateSlot.java new file mode 100644 index 00000000000..6356dd4d2e9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/InternalHomeVisitRemoverFromDateSlot.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.ReverseIndexComparator; +import seedu.address.model.Model; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Person; + +/** + * A class to do the internal removal of home visits when required. + */ +public class InternalHomeVisitRemoverFromDateSlot { + + public final Model model; + private final List personList; + private final List patientDateSlotList; + private final List patientDateSlotIndex; + + /** + * Construct an InternalHomeVisitRemoverFromDateSlot. + * @param model + * @param personList + * @param patientDateSlotList + * @param patientDateSlotIndex + */ + InternalHomeVisitRemoverFromDateSlot(Model model, List personList, List patientDateSlotList, + List patientDateSlotIndex) { + this.model = model; + this.personList = personList; + this.patientDateSlotList = patientDateSlotList; + this.patientDateSlotIndex = patientDateSlotIndex; + } + + /** + * Construct an InternalHomeVisitRemoverFromDateSlot with empty date slot index list. + * @param model + * @param personList + * @param patientDateSlotList + */ + InternalHomeVisitRemoverFromDateSlot(Model model, List personList, List patientDateSlotList) { + this.model = model; + this.personList = personList; + this.patientDateSlotList = patientDateSlotList; + this.patientDateSlotIndex = new ArrayList<>(); + } + + /** + * Remove the respective home visits. + * @return updated homeVisit list + */ + public Boolean removeHomeVisitsForDateSlot() { + Boolean hasRemoveHomeVisits = false; + if (patientDateSlotIndex.isEmpty()) { + hasRemoveHomeVisits = removeHomeVisitsFromAllDateSlot(); + } else { + hasRemoveHomeVisits = removeHomeVisitsFromSpecificDateSlot(); + } + return hasRemoveHomeVisits; + } + + private Boolean removeHomeVisitsFromAllDateSlot() { + Boolean hasRemoveHomeVisits = false; + for (DateSlot dateSlot : patientDateSlotList) { + if (dateSlot.getHasAssigned()) { + removeHomeVisitFromDateSlot(model, personList, dateSlot); + hasRemoveHomeVisits = true; + } + } + return hasRemoveHomeVisits; + } + + private Boolean removeHomeVisitsFromSpecificDateSlot() { + Boolean hasRemoveHomeVisits = false; + ReverseIndexComparator comp = new ReverseIndexComparator(); + patientDateSlotIndex.sort(comp); + for (Index index : patientDateSlotIndex) { + DateSlot dateSlot = patientDateSlotList.get(index.getZeroBased()); + if (dateSlot.getHasAssigned()) { + removeHomeVisitFromDateSlot(model, personList, dateSlot); + hasRemoveHomeVisits = true; + } + } + return hasRemoveHomeVisits; + } + + private void removeHomeVisitFromDateSlot(Model model, List personList, DateSlot dateSlot) { + Long nurseUidNo = dateSlot.getNurseUidNo(); + Person nurse = personList.stream().filter(p -> p.getUid().getUid().equals(nurseUidNo)).findFirst().get(); + List nurseHomeVisitList = ((Nurse) nurse).getHomeVisits(); + List nurseFullyScheduledList = ((Nurse) nurse).getFullyScheduledDates(); + + HomeVisitManager remover = new HomeVisitManager(nurseHomeVisitList, nurseFullyScheduledList); + List updatedHomeVisitList = remover.removeHomeVisitFromDateSlot(dateSlot); + List updatedFullyScheduledList = remover.getFullyScheduledDateList(); + + InternalEditor editor = new InternalEditor(model); + editor.editNurse(nurse, updatedHomeVisitList, updatedFullyScheduledList); + } + + +} + + diff --git a/src/main/java/seedu/address/logic/commands/InternalUnmarkerFromHomeVisit.java b/src/main/java/seedu/address/logic/commands/InternalUnmarkerFromHomeVisit.java new file mode 100644 index 00000000000..3a70bd4b5c3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/InternalUnmarkerFromHomeVisit.java @@ -0,0 +1,117 @@ +package seedu.address.logic.commands; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.ReverseIndexComparator; +import seedu.address.model.Model; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.HomeVisit; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; + +/** + * A class to do the internal unmarking of date slot when required. + */ +public class InternalUnmarkerFromHomeVisit { + + public final Model model; + private final List personList; + private final List nurseHomeVisitList; + private final List nurseHomeVisitIndex; + + /** + * Construct an InternalUnmarkerFromHomeVisit. + * @param model + * @param personList + * @param nurseHomeVisitList + * @param nurseHomeVisitIndex + */ + InternalUnmarkerFromHomeVisit(Model model, List personList, List nurseHomeVisitList, + List nurseHomeVisitIndex) { + this.model = model; + this.personList = personList; + this.nurseHomeVisitList = nurseHomeVisitList; + this.nurseHomeVisitIndex = nurseHomeVisitIndex; + } + + /** + * Construct an InternalUnmarkerFromHomeVisit with empty home visit index list. + * @param model + * @param personList + * @param nurseHomeVisitList + */ + InternalUnmarkerFromHomeVisit(Model model, List personList, List nurseHomeVisitList) { + this.model = model; + this.personList = personList; + this.nurseHomeVisitList = nurseHomeVisitList; + this.nurseHomeVisitIndex = new ArrayList<>(); + } + + /** + * Unmark the respective date slots. + */ + public void unmarkDateSlotForHomeVisit() { + if (nurseHomeVisitIndex.isEmpty()) { + unmarkDateSlotFromAllHomeVisit(); + } else { + unmarkDateSlotFromSpecificHomeVisit(); + } + } + + private void unmarkDateSlotFromAllHomeVisit() { + for (HomeVisit homeVisit : nurseHomeVisitList) { + unmarkDateSlotFromHomeVisit(model, personList, homeVisit); + } + } + + private void unmarkDateSlotFromSpecificHomeVisit() { + ReverseIndexComparator comp = new ReverseIndexComparator(); + nurseHomeVisitIndex.sort(comp); + for (Index index : nurseHomeVisitIndex) { + HomeVisit homeVisit = nurseHomeVisitList.get(index.getZeroBased()); + unmarkDateSlotFromHomeVisit(model, personList, homeVisit); + } + } + + private void unmarkDateSlotFromHomeVisit(Model model, List personList, HomeVisit homeVisit) { + Long patientUidNo = homeVisit.getHomeVisitPatientUidNo(); + Person patient = personList.stream().filter(p -> p.getUid().getUid().equals(patientUidNo)).findFirst().get(); + List patientDateSlotList = ((Patient) patient).getDatesSlots(); + DateSlotManager unmarker = new DateSlotManager(patientDateSlotList); + List updatedDateSlotList = unmarker.unmarkSpecificAssignedFromHomeVisit(homeVisit.getDateSlot()); + + InternalEditor editor = new InternalEditor(model); + editor.editPatient(patient, updatedDateSlotList); + } + + /** + * Unmark the respective date slots. + * @param unavailableDateList + */ + public void unmarkDateSlotForUnavailableDates(List unavailableDateList) { + for (Date date : unavailableDateList) { + unmarkDateSlotForUnavailableDate(date); + } + } + + private void unmarkDateSlotForUnavailableDate(Date date) { + for (HomeVisit homeVisit : nurseHomeVisitList) { + Boolean isSameDate = checkSameDate(homeVisit, date); + if (isSameDate) { + unmarkDateSlotFromHomeVisit(model, personList, homeVisit); + } + } + } + + private Boolean checkSameDate(HomeVisit homeVisit, Date unavailableDate) { + return homeVisit.getDateSlot().getDate().equals(unavailableDate.getDate()); + } + + +} + + + diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..2553aeb6df5 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,24 +1,156 @@ package seedu.address.logic.commands; -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import java.util.Optional; +import java.util.function.Predicate; import seedu.address.model.Model; +import seedu.address.model.category.Category; +import seedu.address.model.person.Address; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** - * Lists all persons in the address book to the user. + * Lists all nurses and patients enrolled in the healthcareXpress database to the user, + * filtered by given specifications. */ public class ListCommand extends Command { + public static final String MESSAGE_ARGUMENTS = "ADDRESS: %s, CATEGORY: %s, GENDER: %s, TAG: %s\n"; + public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all persons with specifications:\n" + MESSAGE_ARGUMENTS; + + public static final String MESSAGE_INVALID_PARAMETERS_IGNORED = + "WARNING: One or more invalid input parameters were ignored\n."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists all enrolled users who fit the specified criteria, " + + "or all enrolled users if no criteria were specified.\n" + + "Parameters: \n" + + " c/ [CATEGORY]\n" + + " g/ [GENDER]\n" + + " a/ [ADDRESS]\n" + + " t/ [TAG]\n" + + "Example: " + COMMAND_WORD + " " + + "c/ n"; + + private final Optional
address; + private final Optional category; + private final Optional gender; + private final Optional tag; + private final boolean parametersAreValid; + + /** + * @param a address to be filtered + * @param c category (nurse/patient) to be filtered + * @param g gender to be filtered + * @param t tag to be filtered + */ + public ListCommand(Optional
a, Optional c, Optional g, Optional t) { + address = a; + category = c; + gender = g; + tag = t; + parametersAreValid = true; + } + /** + * @param a address to be filtered + * @param c category (nurse/patient) to be filtered + * @param g gender to be filtered + * @param t tag to be filtered + * @param p true if all parameters are valid, false if one or more are invalid. + */ + public ListCommand(Optional
a, Optional c, Optional g, Optional t, boolean p) { + address = a; + category = c; + gender = g; + tag = t; + parametersAreValid = p; + } @Override public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + applyFilter(model); + + String filteredAddress = getFilteredAddress(); + String filteredGender = getFilteredGender(); + String filteredCategory = getFilteredCategory(); + String filteredTag = getFilteredTag(); + + String result = String.format(MESSAGE_SUCCESS, + filteredAddress, + filteredCategory, + filteredGender, + filteredTag); + if (!parametersAreValid) { + result += MESSAGE_INVALID_PARAMETERS_IGNORED; + } + return new CommandResult(result); + } + + private String getFilteredAddress() { + return address.orElse(new Address("NIL")).value; + } + + private String getFilteredGender() { + final String[] filteredGender = new String[1]; + gender.ifPresentOrElse(x -> filteredGender[0] = x.gender, () -> filteredGender[0] = "NIL"); + return filteredGender[0]; + } + + private String getFilteredCategory() { + final String[] filteredCategory = new String[1]; + category.ifPresentOrElse(x -> filteredCategory[0] = x.categoryName, () -> filteredCategory[0] = "NIL"); + return filteredCategory[0]; + } + + private String getFilteredTag() { + return tag.orElse(new Tag("NIL")).tagName; + } + + private void applyFilter(Model model) { + Predicate addressMatch = x -> x.getAddress().value.toLowerCase() + .contains(address.orElse(x.getAddress()).value.toLowerCase()); + Predicate categoryMatch = x -> x.getCategory().equalsIgnoreCase(category.orElse(x.getCategory())); + Predicate genderMatch = x -> x.getGender().equalsIgnoreCase(gender.orElse(x.getGender())); + Predicate tagMatch = x -> { + if (x.getTags().isEmpty()) { + return tag.isEmpty(); + } else { + Predicate tagPredicate = y -> { + Tag tagToCompare = tag.orElse((Tag) x.getTags().toArray()[0]); + return y.equals(tagToCompare); + }; + return x.getTags().stream().anyMatch(tagPredicate); + } + }; + Predicate predicate = addressMatch + .and(categoryMatch) + .and(genderMatch) + .and(tagMatch); + model.updateFilteredPersonList(predicate); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ListCommand)) { + return false; + } + + // state check + ListCommand e = (ListCommand) other; + return address.equals(e.address) + && category.equals(e.category) + && gender.equals(e.gender) + && tag.equals(e.tag); } } diff --git a/src/main/java/seedu/address/logic/commands/UndoUnmarkCommand.java b/src/main/java/seedu/address/logic/commands/UndoUnmarkCommand.java new file mode 100644 index 00000000000..8546306a17e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UndoUnmarkCommand.java @@ -0,0 +1,96 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; + +/** + * Undo unmarks a patient's dateslot using their unique id and dateslot index + * when unmark fail to visit wrongly. + */ +public class UndoUnmarkCommand extends Command { + + public static final String COMMAND_WORD = "undounmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Undo Unmarks the patient's dateslot identified by the unique id number in the displayed person list " + + "and dateslot index as visited.\n" + + "Parameters: UID (must be a positive integer) " + PREFIX_DATE_AND_SLOT_INDEX + "Date_Slot_Index" + + "Example: " + COMMAND_WORD + " " + PREFIX_UID + " 1" + PREFIX_DATE_AND_SLOT_INDEX + "2"; + + public static final String MESSAGE_UNDO_UNMARK_PATIENT_SUCCESS = "Undo Unmarked Patient as success visit: %1$s"; + public static final String MESSAGE_INVALID_NURSE_UID = "This uid gives a nurse." + " Please recheck the uid. " + + "Undo Unmark is only for patient."; + public static final String MESSAGE_OUT_OF_BOUND_DATE_AND_SLOT_INDEX = "The given date slot index is out of bounds." + + "Please recheck the index."; + public static final String MESSAGE_INVALID_DATE_AND_SLOT_INDEX_TWO = "This dates has already been marked as " + + "success visited. " + "Cannot undo unmark it as success visit."; + + private final Uid uid; + private final Index dateSlotIndex; + + /** + * Initialises the UndoUnmarkCommand with a valid and non-null {@code Uid} of + * the target patient and + * a valid and non-null {@code Index} of the target DateSlot index. + */ + public UndoUnmarkCommand(Uid uid, Index dateSlotIndex) { + requireAllNonNull(uid, dateSlotIndex); + this.uid = uid; + this.dateSlotIndex = dateSlotIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Optional targetPerson = lastShownList.stream().filter(p -> p.getUid().equals(uid)).findFirst(); + + if (targetPerson.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); + } + + Person personToUndoUnmark = targetPerson.get(); + + if (personToUndoUnmark.isNurse()) { + throw new CommandException(MESSAGE_INVALID_NURSE_UID); + } + + undoUnmarkFailVisited(personToUndoUnmark, model); + + Optional editedPerson = lastShownList.stream().filter(p -> p.getUid().equals(uid)).findFirst(); + Person editedPatient = editedPerson.get(); + return new CommandResult(String.format(MESSAGE_UNDO_UNMARK_PATIENT_SUCCESS, editedPatient)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UndoUnmarkCommand// instanceof handles nulls + && this.uid.equals(((UndoUnmarkCommand) other).uid) // state check + && this.dateSlotIndex.equals(((UndoUnmarkCommand) other).dateSlotIndex)); + } + + private void undoUnmarkFailVisited(Person personToUndoUnmark, Model model) throws CommandException { + List dateSlotList = ((Patient) personToUndoUnmark).getDatesSlots(); + DateSlotManager undoUnmarker = new DateSlotManager(dateSlotList, dateSlotIndex); + List updatedDateSlotList = undoUnmarker.undoUnmarkFailVisited(); + + InternalEditor editor = new InternalEditor(model); + editor.editPatient(personToUndoUnmark, updatedDateSlotList); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/UnmarkCommand.java b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java new file mode 100644 index 00000000000..fafe64ac22d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java @@ -0,0 +1,90 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; + +/** + * Unmarks a patient's dateslot using their unique id and dateslot index when + * fail to visit. + */ +public class UnmarkCommand extends Command { + + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unmarks the patient's dateslot identified by the unique id number in the displayed person list " + + "and dateslot index as fail to visit.\n" + + "Parameters: UID (must be a positive integer) " + PREFIX_DATE_AND_SLOT_INDEX + "Date_Slot_Index" + + "Example: " + COMMAND_WORD + " " + PREFIX_UID + " 1" + PREFIX_DATE_AND_SLOT_INDEX + "2"; + + public static final String MESSAGE_UNMARK_PATIENT_SUCCESS = "Unmarked Patient as fail to visit: %1$s"; + public static final String MESSAGE_INVALID_NURSE_UID = "This uid gives a nurse." + " Please recheck the uid. " + + "Unmark is only for patient."; + + private final Uid uid; + private final Index dateSlotIndex; + + /** + * Initialises the MarkCommand with a valid and non-null {@code Uid} of the + * target patient. + */ + public UnmarkCommand(Uid uid, Index dateSlotIndex) { + requireAllNonNull(uid, dateSlotIndex); + this.uid = uid; + this.dateSlotIndex = dateSlotIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Optional targetPerson = lastShownList.stream().filter(p -> p.getUid().equals(uid)).findFirst(); + + if (targetPerson.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID); + } + + Person personToUnmark = targetPerson.get(); + + if (personToUnmark.isNurse()) { + throw new CommandException(MESSAGE_INVALID_NURSE_UID); + } + + unmarkSuccessVisit(personToUnmark, model); + + Optional editedPerson = lastShownList.stream().filter(p -> p.getUid().equals(uid)).findFirst(); + Person editedPatient = editedPerson.get(); + return new CommandResult(String.format(MESSAGE_UNMARK_PATIENT_SUCCESS, editedPatient)); + } + + private void unmarkSuccessVisit(Person personToUnmark, Model model) throws CommandException { + List dateSlotList = ((Patient) personToUnmark).getDatesSlots(); + DateSlotManager unmarker = new DateSlotManager(dateSlotList, dateSlotIndex); + List updatedDateSlotList = unmarker.unmarkSuccessVisited(); + + InternalEditor editor = new InternalEditor(model); + editor.editPatient(personToUnmark, updatedDateSlotList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnmarkCommand// instanceof handles nulls + && this.uid.equals(((UnmarkCommand) other).uid) // state check + && this.dateSlotIndex.equals(((UnmarkCommand) other).dateSlotIndex)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UpdateContactCommand.java b/src/main/java/seedu/address/logic/commands/UpdateContactCommand.java new file mode 100644 index 00000000000..08b6c835bbb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UpdateContactCommand.java @@ -0,0 +1,131 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.category.Category; +import seedu.address.model.person.BasePerson; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.NextOfKin; +import seedu.address.model.person.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Physician; +import seedu.address.model.person.Uid; + +/** + * Updates contact details of attending physician or next of kin to a patient. + */ +public class UpdateContactCommand extends Command { + public static final String COMMAND_WORD = "updatecontact"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Update next of kin or attending physician contact details " + + "to the selected patient. \n" + + "Parameters: id/ [UID]" + + "n/ [CONTACT NAME]\n" + + "p/ [CONTACT PHONE]\n" + + "e/ [CONTACT EMAIL]\n" + + "c/ [CONTACT CATEGORY]: either D for Physician or K for Next of Kin\n" + + "Example: " + COMMAND_WORD + " id/3 " + + "n/ John Doe p/ 81234567 e/ johndoe@example.com c/ D"; + + public static final String MESSAGE_UPDATE_CONTACT_SUCCESS = "Added contact details to patient with UID: %s, " + + "Contact Name: %s, Phone: %s, Email: %s, Category: %s"; + + private final Uid uid; + + private final Name name; + + private final Phone phone; + + private final Email email; + + private final Category category; + + /** + * Create a new UpdateContactCommand. + * + * @param i patient uid + * @param n contact name + * @param p contact phone number + * @param e contact email + * @param c contact category + */ + public UpdateContactCommand(Uid i, Name n, Phone p, Email e, Category c) { + uid = i; + name = n; + email = e; + phone = p; + category = c; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + Patient patientToEdit = getPersonToEdit(model); + + Patient editedPatient = updateContact(patientToEdit); + + model.setPerson(patientToEdit, editedPatient); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(String.format(MESSAGE_UPDATE_CONTACT_SUCCESS, uid, name, phone, email, category)); + } + + private Patient getPersonToEdit(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + Person personToEdit = lastShownList.stream() + .filter(x -> x.getUid().equals(uid)) + .findAny() + .orElseThrow(() -> new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_UID)); + if (!personToEdit.getCategory().equals(new Category(Category.PATIENT_SYMBOL))) { + throw new CommandException(Messages.MESSAGE_UPDATECONTACT_INVALID_CATEGORY); + } + return (Patient) personToEdit; + } + + private Patient updateContact(Patient patientToEdit) throws CommandException { + BasePerson updatedContact; + if (category.equals(new Category(Category.PHYSICIAN_SYMBOL))) { + updatedContact = new Physician(name, phone, email); + return new Patient(patientToEdit.getUid(), patientToEdit.getName(), patientToEdit.getGender(), + patientToEdit.getPhone(), patientToEdit.getEmail(), patientToEdit.getAddress(), + patientToEdit.getTags(), patientToEdit.getDatesSlots(), + Optional.of((Physician) updatedContact), patientToEdit.getNextOfKin()); + } else if (category.equals(new Category(Category.NEXTOFKIN_SYMBOL))) { + updatedContact = new NextOfKin(name, phone, email); + return new Patient(patientToEdit.getUid(), patientToEdit.getName(), patientToEdit.getGender(), + patientToEdit.getPhone(), patientToEdit.getEmail(), patientToEdit.getAddress(), + patientToEdit.getTags(), patientToEdit.getDatesSlots(), + patientToEdit.getAttendingPhysician(), Optional.of((NextOfKin) updatedContact)); + } + throw new CommandException(Messages.MESSAGE_UPDATECONTACT_INVALID_CONTACT_CATEGORY); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UpdateContactCommand)) { + return false; + } + + // state check + UpdateContactCommand e = (UpdateContactCommand) other; + return uid.equals(e.uid) + && name.equals(e.name) + && phone.equals(e.phone) + && email.equals(e.email) + && category.equals(e.category); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cd..5469b1193c3 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands.exceptions; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a {@link seedu.address.logic.commands.Command}. */ public class CommandException extends Exception { public CommandException(String message) { @@ -9,7 +9,8 @@ public CommandException(String message) { } /** - * Constructs a new {@code CommandException} with the specified detail {@code message} and {@code cause}. + * Constructs a new {@code CommandException} with the specified detail + * {@code message} and {@code cause}. */ public CommandException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..8aa631cba2a 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,22 +1,37 @@ package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_CATEGORY; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE; +import static seedu.address.model.category.Category.NURSE_SYMBOL; +import static seedu.address.model.category.Category.PATIENT_SYMBOL; +import java.util.List; 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.category.Category; import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; import seedu.address.model.person.Name; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -24,37 +39,62 @@ */ public class AddCommandParser implements Parser { + /** + * 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()); + } + /** * 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_CATEGORY, PREFIX_NAME, PREFIX_GENDER, + PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_DATE_AND_SLOT, PREFIX_UNAVAILABLE_DATE); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_CATEGORY, PREFIX_NAME, PREFIX_GENDER, + PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } + Boolean dateSlotsPresent = arePrefixesPresent(argMultimap, PREFIX_DATE_AND_SLOT); + Boolean unavailableDatesPresent = arePrefixesPresent(argMultimap, PREFIX_UNAVAILABLE_DATE); + + Category category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY).get()); + Boolean isPatient = category.categoryName.equals(PATIENT_SYMBOL); + Boolean isNurse = category.categoryName.equals(NURSE_SYMBOL); + if (isNurse == isPatient) { + throw new ParseException(MESSAGE_INVALID_CATEGORY); + } + if (isPatient && unavailableDatesPresent) { + throw new ParseException(AddCommand.MESSAGE_INVALID_FIELD_PATIENT); + } + if (isNurse && dateSlotsPresent) { + throw new ParseException(AddCommand.MESSAGE_INVALID_FIELD_NURSE); + } + + Uid id = new Uid(); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + List dateTimeSlotList = ParserUtil.parseDatesSlots(argMultimap.getAllValues(PREFIX_DATE_AND_SLOT)); + List unavailableDateList = ParserUtil.parseDates(argMultimap.getAllValues(PREFIX_UNAVAILABLE_DATE)); - Person person = new Person(name, phone, email, address, tagList); - + Person person; + if (isNurse) { + person = new Nurse(id, name, gender, phone, email, address, tagList, unavailableDateList); + } + person = new Patient(id, name, gender, phone, email, address, tagList, dateTimeSlotList); 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/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8e..884c8a7500d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -9,20 +9,26 @@ /** * Stores mapping of prefixes to their respective arguments. * Each key may be associated with multiple argument values. - * Values for a given key are stored in a list, and the insertion ordering is maintained. - * Keys are unique, but the list of argument values may contain duplicate argument values, i.e. the same argument value + * Values for a given key are stored in a list, and the insertion ordering is + * maintained. + * Keys are unique, but the list of argument values may contain duplicate + * argument values, i.e. the same argument value * can be inserted multiple times for the same prefix. */ public class ArgumentMultimap { - /** Prefixes mapped to their respective arguments**/ + /** + * Prefixes mapped to their respective arguments + **/ private final Map> argMultimap = new HashMap<>(); /** * Associates the specified argument value with {@code prefix} key in this map. - * If the map previously contained a mapping for the key, the new value is appended to the list of existing values. + * If the map previously contained a mapping for the key, the new value is + * appended to the list of existing values. * - * @param prefix Prefix key with which the specified argument value is to be associated + * @param prefix Prefix key with which the specified argument value is to be + * associated * @param argValue Argument value to be associated with the specified prefix key */ public void put(Prefix prefix, String argValue) { @@ -41,8 +47,10 @@ public Optional getValue(Prefix prefix) { /** * Returns all values of {@code prefix}. - * If the prefix does not exist or has no values, this will return an empty list. - * Modifying the returned list will not affect the underlying data structure of the ArgumentMultimap. + * If the prefix does not exist or has no values, this will return an empty + * list. + * Modifying the returned list will not affect the underlying data structure of + * the ArgumentMultimap. */ public List getAllValues(Prefix prefix) { if (!argMultimap.containsKey(prefix)) { @@ -52,7 +60,8 @@ public List getAllValues(Prefix prefix) { } /** - * Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces. + * Returns the preamble (text before the first valid prefix). Trims any + * leading/trailing spaces. */ public String getPreamble() { return getValue(new Prefix("")).orElse(""); diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..2e34adfd79a 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -6,22 +6,30 @@ import java.util.stream.Collectors; /** - * Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
- * 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
- * 2. Leading and trailing whitespaces of an argument value will be discarded.
- * 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
+ * Tokenizes arguments string of the form: + * {@code preamble value value ...}
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes + * are {@code t/ k/ m/}.
+ * 1. An argument's value can be an empty string e.g. the value of {@code k/} in + * the above example.
+ * 2. Leading and trailing whitespaces of an argument value will be + * discarded.
+ * 3. An argument may be repeated and all its values will be accumulated e.g. + * the value of {@code t/} + * in the above example.
*/ public class ArgumentTokenizer { /** - * Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their - * respective argument values. Only the given prefixes will be recognized in the arguments string. + * Tokenizes an arguments string and returns an {@code ArgumentMultimap} object + * that maps prefixes to their + * respective argument values. Only the given prefixes will be recognized in the + * arguments string. * - * @param argsString Arguments string of the form: {@code preamble value value ...} + * @param argsString Arguments string of the form: + * {@code preamble value value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List positions = findAllPrefixPositions(argsString, prefixes); @@ -31,9 +39,10 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { /** * Finds all zero-based prefix positions in the given arguments string. * - * @param argsString Arguments string of the form: {@code preamble value value ...} + * @param argsString Arguments string of the form: + * {@code preamble value value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) @@ -62,7 +71,7 @@ private static List findPrefixPositions(String argsString, Prefi * {@code argsString} starting from index {@code fromIndex}. An occurrence * is valid if there is a whitespace before {@code prefix}. Returns -1 if no * such occurrence can be found. - * + *

* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and * {@code fromIndex} = 0, this method returns -1 as there are no valid * occurrences of "p/" with whitespace before it. However, if @@ -76,13 +85,17 @@ private static int findPrefixPosition(String argsString, String prefix, int from } /** - * Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the - * extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in + * Extracts prefixes and their argument values, and returns an + * {@code ArgumentMultimap} object that maps the + * extracted prefixes to their respective arguments. Prefixes are extracted + * based on their zero-based positions in * {@code argsString}. * - * @param argsString Arguments string of the form: {@code preamble value value ...} - * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @param argsString Arguments string of the form: + * {@code preamble value value ...} + * @param prefixPositions Zero-based positions of all prefixes in + * {@code argsString} + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { @@ -110,12 +123,13 @@ private static ArgumentMultimap extractArguments(String argsString, List { + + /** + * Parses the given {@code String} of arguments in the context of the + * AssignCommand + * and returns an AssignCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AssignCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX); + + List uidInput = argMultimap.getAllValues(PREFIX_UID); + + if (uidInput.size() != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE)); + } + + String uid1String = uidInput.get(0); + String uid2String = uidInput.get(1); + + Uid uid1 = parseUid(uid1String); + Uid uid2 = parseUid(uid2String); + + List indexList = parseIndexes(argMultimap.getAllValues(PREFIX_DATE_AND_SLOT_INDEX)); + + return new AssignCommand(uid1, uid2, indexList); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..366bcd9a018 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -1,15 +1,23 @@ package seedu.address.logic.parser; /** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + * Contains Command Line Interface (CLI) syntax definitions common to multiple + * commands */ public class CliSyntax { /* Prefix definitions */ + public static final Prefix PREFIX_CATEGORY = new Prefix("c/"); + public static final Prefix PREFIX_UID = new Prefix("id/"); public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_GENDER = new Prefix("g/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_DATE_AND_SLOT = new Prefix("ds/"); + public static final Prefix PREFIX_DATE_AND_SLOT_INDEX = new Prefix("dsi/"); + public static final Prefix PREFIX_UNAVAILABLE_DATE = new Prefix("ud/"); + public static final Prefix PREFIX_UNAVAILABLE_DATE_INDEX = new Prefix("udi/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeassignCommandParser.java b/src/main/java/seedu/address/logic/parser/DeassignCommandParser.java new file mode 100644 index 00000000000..3f3adeac757 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeassignCommandParser.java @@ -0,0 +1,47 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; +import static seedu.address.logic.parser.ParserUtil.parseIndexes; +import static seedu.address.logic.parser.ParserUtil.parseUid; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeassignCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; + +/** + * Parses input arguments and creates a new Assign object + */ +public class DeassignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * DeassignCommand + * and returns a DeassignCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeassignCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX); + + Uid uid; + + if (argMultimap.getValue(PREFIX_UID).isPresent()) { + uid = parseUid(argMultimap.getValue(PREFIX_UID).get()); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + List indexList = parseIndexes(argMultimap.getAllValues(PREFIX_DATE_AND_SLOT_INDEX)); + + return new DeassignCommand(uid, indexList); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 522b93081cc..4df17588494 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,10 +1,13 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.stream.Stream; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; /** * Parses input arguments and creates a new DeleteCommand object @@ -12,18 +15,31 @@ public class DeleteCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand + * 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()); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID); + + if (!arePrefixesPresent(argMultimap, PREFIX_UID) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); } - } + Uid uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID).get()); + + return new DeleteCommand(uid); + } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..04c083a8824 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,13 +3,20 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UNAVAILABLE_DATE_INDEX; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -17,6 +24,9 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -25,27 +35,32 @@ public class EditCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the EditCommand + * Parses the given {@code String} of arguments in the context of the + * EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID, PREFIX_NAME, + PREFIX_GENDER, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_DATE_AND_SLOT, PREFIX_TAG, + PREFIX_DATE_AND_SLOT_INDEX, PREFIX_UNAVAILABLE_DATE, PREFIX_UNAVAILABLE_DATE_INDEX); - Index index; + Uid uid; - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + if (argMultimap.getValue(PREFIX_UID).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); } + uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID).get()); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); + } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } @@ -55,18 +70,29 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } + + parseDatesSlotsForEdit(argMultimap.getAllValues(PREFIX_DATE_AND_SLOT)) + .ifPresent(editPersonDescriptor::setDatesSlots); + parseDateSlotIndexesForEdit(argMultimap.getAllValues(PREFIX_DATE_AND_SLOT_INDEX)) + .ifPresent(editPersonDescriptor::setDateSlotIndexes); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseDatesForEdit(argMultimap.getAllValues(PREFIX_UNAVAILABLE_DATE)) + .ifPresent(editPersonDescriptor::setUnavailableDates); + parseDateIndexesForEdit(argMultimap.getAllValues(PREFIX_UNAVAILABLE_DATE_INDEX)) + .ifPresent(editPersonDescriptor::setDateIndexes); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(uid, editPersonDescriptor); } /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * Parses {@code Collection tags} into a {@code Set} if + * {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be + * parsed into a * {@code Set} containing zero tags. */ private Optional> parseTagsForEdit(Collection tags) throws ParseException { @@ -79,4 +105,79 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Parses {@code Collection datesSlots} into a {@code List} if + * {@code dateSlots} is non-empty. + * If {@code datesSlots} contain only one element which is an empty string, it + * will be parsed into a + * {@code List} containing zero dateTime. + */ + private Optional> parseDatesSlotsForEdit(Collection datesSlots) throws ParseException { + assert datesSlots != null; + + if (datesSlots.isEmpty()) { + return Optional.empty(); + } + Collection dateSlotList = datesSlots.size() == 1 && datesSlots.contains("") + ? Collections.emptyList() + : datesSlots; + + return Optional.of(ParserUtil.parseDatesSlots(dateSlotList)); + } + + /** + * Parses {@code Collection dateSlotIndexes} into a + * {@code List} + * if {@code dateSlotIndexes} is non-empty. + */ + private Optional> parseDateSlotIndexesForEdit(Collection dateSlotIndexes) + throws ParseException { + assert dateSlotIndexes != null; + + if (dateSlotIndexes.isEmpty()) { + return Optional.empty(); + } + Collection dateSlotIndexList = dateSlotIndexes.size() == 1 && dateSlotIndexes.contains("") + ? Collections.emptyList() + : dateSlotIndexes; + return Optional.of(ParserUtil.parseIndexes(dateSlotIndexList)); + } + + /** + * Parses {@code Collection dates} into a {@code List} if + * {@code date} is non-empty. + * If {@code dates} contain only one element which is an empty string, it will + * be parsed into a + * {@code List} containing zero date. + */ + private Optional> parseDatesForEdit(Collection dates) throws ParseException { + assert dates != null; + + if (dates.isEmpty()) { + return Optional.empty(); + } + Collection dateList = dates.size() == 1 && dates.contains("") + ? Collections.emptyList() + : dates; + + return Optional.of(ParserUtil.parseDates(dateList)); + } + + /** + * Parses {@code Collection dateIndexes} into a {@code List} + * if {@code dateIndexes} is non-empty. + */ + private Optional> parseDateIndexesForEdit(Collection dateIndexes) + throws ParseException { + assert dateIndexes != null; + + if (dateIndexes.isEmpty()) { + return Optional.empty(); + } + Collection dateIndexList = dateIndexes.size() == 1 && dateIndexes.contains("") + ? Collections.emptyList() + : dateIndexes; + return Optional.of(ParserUtil.parseIndexes(dateIndexList)); + } + } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..911de8c8a4a 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -14,8 +14,10 @@ public class FindCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the FindCommand + * Parses the given {@code String} of arguments in the context of the + * FindCommand * and returns a FindCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/HealthcareXpressParser.java similarity index 66% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/HealthcareXpressParser.java index 1e466792b46..6bdc6694dd0 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/HealthcareXpressParser.java @@ -7,20 +7,27 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AssignCommand; +import seedu.address.logic.commands.CheckSimilarCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeassignCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.UndoUnmarkCommand; +import seedu.address.logic.commands.UnmarkCommand; +import seedu.address.logic.commands.UpdateContactCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; /** * Parses user input. */ -public class AddressBookParser { +public class HealthcareXpressParser { /** * Used for initial separation of command word and args. @@ -30,11 +37,12 @@ public class AddressBookParser { /** * Parses user input into command for execution. * + * @param model model * @param userInput full user input string * @return the command based on the user input * @throws ParseException if the user input does not conform the expected format */ - public Command parseCommand(String userInput) throws ParseException { + public Command parseCommand(String userInput, Model model) throws ParseException { final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); @@ -60,7 +68,19 @@ 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 UnmarkCommand.COMMAND_WORD: + return new UnmarkCommandParser().parse(arguments); + + case UndoUnmarkCommand.COMMAND_WORD: + return new UndoUnmarkCommandParser().parse(arguments); + + case AssignCommand.COMMAND_WORD: + return new AssignCommandParser().parse(arguments); + + case DeassignCommand.COMMAND_WORD: + return new DeassignCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -68,6 +88,12 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case UpdateContactCommand.COMMAND_WORD: + return new UpdateContactCommandParser().parse(arguments); + + case CheckSimilarCommand.COMMAND_WORD: + return new CheckSimilarCommand(); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } 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..641c382628a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,114 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.model.category.Category; +import seedu.address.model.person.Address; +import seedu.address.model.person.Gender; +import seedu.address.model.tag.Tag; + +/** + * Parses user input for the list command. + */ +public class ListCommandParser implements Parser { + + private boolean parametersAreValid = true; + + /** + * Parses user input for the list command. + * + * @param args user input, for filtering the list of displayed users + * @return Filtered list, or list of all users if no filters were specified. + */ + public ListCommand parse(String args) { + if (args.length() == 0) { + return new ListCommand(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); + } + parametersAreValid = true; + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_ADDRESS, + PREFIX_CATEGORY, + PREFIX_GENDER, + PREFIX_TAG); + + Optional

address = getFilteredAddress(argMultimap); + Optional tag = getFilteredTag(argMultimap); + Optional category = getFilteredCategory(argMultimap); + Optional gender = getFilteredGender(argMultimap); + + return new ListCommand(address, category, gender, tag, parametersAreValid); + } + + private Optional getFilteredTag(ArgumentMultimap argumentMultimap) { + + List> tag = new ArrayList<>(); + argumentMultimap.getValue(PREFIX_TAG).ifPresentOrElse( + x -> { + if (Address.isValidAddress(x)) { + tag.add(Optional.of(new Tag(x))); + } else { + tag.add(Optional.empty()); + parametersAreValid = false; + } + }, () -> tag.add(Optional.empty())); + assert (tag.size() == 1); + return tag.get(0); + } + + private Optional
getFilteredAddress(ArgumentMultimap argumentMultimap) { + + List> address = new ArrayList<>(); + argumentMultimap.getValue(PREFIX_ADDRESS).ifPresentOrElse( + x -> { + if (Address.isValidAddress(x)) { + address.add(Optional.of(new Address(x))); + } else { + address.add(Optional.empty()); + parametersAreValid = false; + } + }, () -> address.add(Optional.empty())); + assert (address.size() == 1); + return address.get(0); + } + + private Optional getFilteredCategory(ArgumentMultimap argumentMultimap) { + + List> category = new ArrayList<>(); + argumentMultimap.getValue(PREFIX_CATEGORY).ifPresentOrElse( + x -> { + if (Category.isValidCategoryName(x.toUpperCase())) { + category.add(Optional.of(new Category(x.toUpperCase()))); + } else { + category.add(Optional.empty()); + parametersAreValid = false; + } + }, () -> category.add(Optional.empty())); + assert (category.size() == 1); + return category.get(0); + } + + private Optional getFilteredGender(ArgumentMultimap argumentMultimap) { + + List> gender = new ArrayList<>(); + argumentMultimap.getValue(PREFIX_GENDER).ifPresentOrElse( + x -> { + if (Gender.isValidGender(x.toUpperCase())) { + gender.add(Optional.of(new Gender(x.toUpperCase()))); + } else { + gender.add(Optional.empty()); + parametersAreValid = false; + } + }, () -> gender.add(Optional.empty())); + assert (gender.size() == 1); + return gender.get(0); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..b97b7bf910d 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -4,13 +4,16 @@ import seedu.address.logic.parser.exceptions.ParseException; /** - * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. + * Represents a Parser that is able to parse user input into a {@code Command} + * of type {@code T}. */ public interface Parser { /** * Parses {@code userInput} into a command and returns it. - * @throws ParseException if {@code userInput} does not conform the expected format + * + * @throws ParseException if {@code userInput} does not conform the expected + * format */ T parse(String userInput) throws ParseException; } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..3314f1a2525 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,30 +2,44 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.category.Category; import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** - * Contains utility methods used for parsing strings in the various *Parser classes. + * Contains utility methods used for parsing strings in the various *Parser + * classes. */ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + // public static final String MESSAGE_INVALID_DATE_TIME_INDEX = "Date time index + // is not a + // non-zero unsigned integer."; /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * 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). + * + * @throws ParseException if the specified index is invalid (not non-zero + * unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); @@ -35,6 +49,22 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String id} into a {@code Uid}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code id} is invalid. + */ + public static Uid parseUid(String id) throws ParseException { + requireNonNull(id); + String trimmedId = id.trim(); + if (!Uid.isValidUid(trimmedId)) { + throw new ParseException(Uid.MESSAGE_CONSTRAINTS); + } + Long parsedId = Long.parseLong(trimmedId); + return new Uid(parsedId); + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -50,6 +80,78 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses {@code Collection Index} into a {@code List}. + */ + public static List parseIndexes(Collection indexes) throws ParseException { + requireNonNull(indexes); + List indexList = new ArrayList<>(); + for (String index : indexes) { + indexList.add(parseIndex(index)); + } + return indexList; + } + + /** + * Parses a {@code String dateSlot} into a {@code DateSlot}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code dateSlot} is invalid. + */ + public static DateSlot parseDateSlot(String dateSlot) throws ParseException { + requireNonNull(dateSlot); + String trimmedDateTimeSlot = dateSlot.trim(); + + if (!DateSlot.isValidDateSlot(trimmedDateTimeSlot)) { + throw new ParseException(DateSlot.MESSAGE_CONSTRAINTS); + } + return new DateSlot(trimmedDateTimeSlot); + } + + /** + * Parses {@code Collection dateSlot} into a {@code List}. + */ + public static List parseDatesSlots(Collection dateSlots) throws ParseException { + requireNonNull(dateSlots); + List dateTimeSlotList = new ArrayList(); + for (String dateSlot : dateSlots) { + dateTimeSlotList.add(parseDateSlot(dateSlot)); + } + return dateTimeSlotList; + } + + /** + * Parses a {@code String date} into a {@code Date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static Date parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + + if (!Date.isValidDateFormat(trimmedDate)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS); + } + + if (!Date.isValidDate(trimmedDate)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS_VALID_DATE); + } + return new Date(trimmedDate); + } + + /** + * Parses {@code Collection date} into a {@code List}. + */ + public static List parseDates(Collection dates) throws ParseException { + requireNonNull(dates); + List dateList = new ArrayList(); + for (String date : dates) { + dateList.add(parseDate(date)); + } + return dateList; + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +223,37 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String gender} into a {@code Gender}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code gender} is invalid. + */ + public static Gender parseGender(String gender) throws ParseException { + requireNonNull(gender); + String trimmedGender = gender.trim(); + trimmedGender = Gender.formatMisspelling(trimmedGender); + if (!Gender.isValidGender(trimmedGender)) { + throw new ParseException(Gender.MESSAGE_CONSTRAINTS); + } + return new Gender(trimmedGender); + } + + /** + * Parses a {@code String category} into a {@code Category}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code category} is invalid. + */ + public static Category parseCategory(String category) throws ParseException { + requireNonNull(category); + String trimmedCategory = category.trim(); + trimmedCategory = Category.formatMisspelling(trimmedCategory); + if (!Category.isValidCategoryName(trimmedCategory)) { + throw new ParseException(Category.MESSAGE_CONSTRAINTS); + } + return new Category(trimmedCategory); + } + } diff --git a/src/main/java/seedu/address/logic/parser/UndoUnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UndoUnmarkCommandParser.java new file mode 100644 index 00000000000..e5ad4bea65b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UndoUnmarkCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UndoUnmarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; + +/** + * Parses input arguments and creates a new MarkCommand object + */ +public class UndoUnmarkCommandParser implements Parser { + + /** + * 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()); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * UndoUnmarkCommand + * and returns an UndoUnmarkCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UndoUnmarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UndoUnmarkCommand.MESSAGE_USAGE)); + } + Uid uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID).get()); + Index dateSlotIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_DATE_AND_SLOT_INDEX).get()); + + return new UndoUnmarkCommand(uid, dateSlotIndex); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java new file mode 100644 index 00000000000..7cd910de412 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_AND_SLOT_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnmarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; + +/** + * Parses input arguments and creates a new UnmarkCommand object + */ +public class UnmarkCommandParser implements Parser { + + /** + * 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()); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * UnmarkCommand + * and returns an UnmarkCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnmarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_UID, PREFIX_DATE_AND_SLOT_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + } + Uid uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID).get()); + Index dateSlotIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_DATE_AND_SLOT_INDEX).get()); + + return new UnmarkCommand(uid, dateSlotIndex); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UpdateContactCommandParser.java b/src/main/java/seedu/address/logic/parser/UpdateContactCommandParser.java new file mode 100644 index 00000000000..cd4200dcac3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UpdateContactCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.function.Supplier; + +import seedu.address.logic.commands.UpdateContactCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.category.Category; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; + +/** + * Parses user input for the update contact command. + */ +public class UpdateContactCommandParser implements Parser { + + @Override + public UpdateContactCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_UID, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_CATEGORY); + + Supplier exceptionSupplier = () -> new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UpdateContactCommand.MESSAGE_USAGE)); + + Uid uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID) + .orElseThrow(exceptionSupplier)); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME) + .orElseThrow(exceptionSupplier)); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE) + .orElseThrow(exceptionSupplier)); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL) + .orElseThrow(exceptionSupplier)); + Category category = ParserUtil.parseCategory(argMultimap.getValue(PREFIX_CATEGORY) + .orElseThrow(exceptionSupplier)); + + return new UpdateContactCommand(uid, name, phone, email, category); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..2ce90c03a9f 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Optional; import javafx.collections.ObservableList; import seedu.address.model.person.Person; @@ -17,17 +18,21 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * The 'unusual' code block below is a non-static initialization block, + * sometimes used to avoid duplication + * between constructors. See + * https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. + * Note that non-static init blocks are not recommended to use. There are other + * ways to avoid duplication + * among constructors. */ { persons = new UniquePersonList(); } - public AddressBook() {} + public AddressBook() { + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -59,13 +64,32 @@ public void resetData(ReadOnlyAddressBook newData) { //// person-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in + * the address book. */ public boolean hasPerson(Person person) { requireNonNull(person); return persons.contains(person); } + /** + * Returns true if a person with a similar identity as {@code person} exists in + * the address book. + */ + public boolean hasSimilarPerson(Person person) { + requireNonNull(person); + return persons.containsSimilar(person); + } + + /** + * Returns an optional with a person that has a similar identity as + * {@code person} exists in the address book. + */ + public Optional findSimilarPerson(Person person) { + requireNonNull(person); + return persons.findSimilarPerson(person); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -75,9 +99,11 @@ public void addPerson(Person p) { } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given person {@code target} in the list with + * {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the address book. */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); @@ -110,7 +136,7 @@ public ObservableList getPersonList() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && persons.equals(((AddressBook) other).persons)); } @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..973b7419664 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,6 +1,7 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Predicate; import javafx.collections.ObservableList; @@ -11,19 +12,21 @@ * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - /** - * Replaces user prefs data with the data in {@code userPrefs}. + * {@code Predicate} that always evaluate to true */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); + Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; /** * Returns the user prefs. */ ReadOnlyUserPrefs getUserPrefs(); + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + /** * Returns the user prefs' GUI settings. */ @@ -44,19 +47,34 @@ public interface Model { */ void setAddressBookFilePath(Path addressBookFilePath); + /** + * Returns the AddressBook + */ + ReadOnlyAddressBook getAddressBook(); + /** * Replaces address book data with the data in {@code addressBook}. */ void setAddressBook(ReadOnlyAddressBook addressBook); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in + * the address book. */ boolean hasPerson(Person person); + /** + * Returns true if a person is similar to a {@code person} that exists in the + * address book. + */ + boolean hasSimilarPerson(Person person); + + /** + * Returns an optional of a person that is similar to a {@code person} that + * exists in the address book. + */ + Optional findSimilarPerson(Person person); + /** * Deletes the given person. * The person must exist in the address book. @@ -72,16 +90,22 @@ public interface Model { /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the address book. */ 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}. + * 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); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..b42826b9f68 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,7 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; @@ -40,17 +41,18 @@ public ModelManager() { this(new AddressBook(), new UserPrefs()); } - //=========== UserPrefs ================================================================================== + // =========== UserPrefs + // ================================================================================== @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; } @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); } @Override @@ -75,16 +77,17 @@ public void setAddressBookFilePath(Path addressBookFilePath) { userPrefs.setAddressBookFilePath(addressBookFilePath); } - //=========== AddressBook ================================================================================ + // =========== AddressBook + // ================================================================================ @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public ReadOnlyAddressBook getAddressBook() { + return addressBook; } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void setAddressBook(ReadOnlyAddressBook addressBook) { + this.addressBook.resetData(addressBook); } @Override @@ -93,6 +96,17 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasSimilarPerson(Person person) { + requireNonNull(person); + return addressBook.hasSimilarPerson(person); + } + + @Override + public Optional findSimilarPerson(Person person) { + return addressBook.findSimilarPerson(person); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -107,14 +121,15 @@ public void addPerson(Person person) { @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); } - //=========== Filtered Person List Accessors ============================================================= + // =========== Filtered Person List Accessors + // ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Person} backed by the + * internal list of * {@code versionedAddressBook} */ @Override diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..01338e06db9 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,12 +14,13 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data", "addressbook.json"); /** * Creates a {@code UserPrefs} with default values. */ - public UserPrefs() {} + public UserPrefs() { + } /** * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. @@ -61,7 +62,7 @@ public boolean equals(Object other) { if (other == this) { return true; } - if (!(other instanceof UserPrefs)) { //this handles null as well. + if (!(other instanceof UserPrefs)) { // this handles null as well. return false; } @@ -79,8 +80,8 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("Gui Settings : ").append(guiSettings); + sb.append("\nLocal data file location : ").append(addressBookFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/category/Category.java b/src/main/java/seedu/address/model/category/Category.java new file mode 100644 index 00000000000..c815da019eb --- /dev/null +++ b/src/main/java/seedu/address/model/category/Category.java @@ -0,0 +1,114 @@ +package seedu.address.model.category; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a Category in the address book. + * Guarantees: immutable; name is valid as declared in + * {@link #isValidCategoryName(String)} + */ +public class Category { + + public static final String MESSAGE_CONSTRAINTS = "Category names can be only N for nurse, " + + "P for patient, D for physician or K for next of kin."; + public static final String NURSE_SYMBOL = "N"; + public static final String PATIENT_SYMBOL = "P"; + public static final String PHYSICIAN_SYMBOL = "D"; + public static final String NEXTOFKIN_SYMBOL = "K"; + public static final String VALIDATION_REGEX = "[" + NURSE_SYMBOL + "|" + PATIENT_SYMBOL + "|" + + PHYSICIAN_SYMBOL + "|" + NEXTOFKIN_SYMBOL + "]"; + public static final ArrayList COMMON_NURSE_MISSPELLINGS = new ArrayList<>( + List.of("nurse", "nurses", "n")); + public static final ArrayList COMMON_PATIENT_MISSPELLINGS = new ArrayList<>( + List.of("patient", "patients", "p")); + + public final String categoryName; + + /** + * Constructs a {@code Category}. + * + * @param categoryName A valid category name. + */ + public Category(String categoryName) { + requireNonNull(categoryName); + checkArgument(isValidCategoryName(categoryName), MESSAGE_CONSTRAINTS); + this.categoryName = categoryName; + } + + /** + * Catches and formats any common misspellings as defined in the common + * misspelling constant of nurses and patients + * + * @param test The String to be tested + * @return THe nurse or patient symbol if it is a misspelling and the original + * text otherwise + */ + public static String formatMisspelling(String test) { + if (COMMON_NURSE_MISSPELLINGS.contains(test.trim().toLowerCase())) { + return NURSE_SYMBOL; + } + if (COMMON_PATIENT_MISSPELLINGS.contains(test.trim().toLowerCase())) { + return PATIENT_SYMBOL; + } + return test; + } + + /** + * Returns true if a given string is a valid category name. + */ + public static boolean isValidCategoryName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.category.Category // instanceof handles nulls + && categoryName.equals(((seedu.address.model.category.Category) other).categoryName)); // state check + } + + @Override + public int hashCode() { + return categoryName.hashCode(); + } + + public String toString() { + return categoryName; + } + + /** + * Checks if the two category objects are equal, ignoring case. + * + * @param other other object to be checked against + * @return true if both categories are the same, false otherwise + */ + public boolean equalsIgnoreCase(Object other) { + return other == this + || (other instanceof Category) + && categoryName.equalsIgnoreCase(((Category) other).categoryName); + } + + public boolean isNurse() { + return categoryName.equals(NURSE_SYMBOL); + } + + public boolean isPatient() { + return categoryName.equals(PATIENT_SYMBOL); + } + + public boolean isPhysician() { + return categoryName.equals(PHYSICIAN_SYMBOL); + } + + public boolean isNextOfKin() { + return categoryName.equals(NEXTOFKIN_SYMBOL); + } + + public String toFormattedString() { + return String.format("Category: %s;", categoryName); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..c4cb9f3b303 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -5,7 +5,8 @@ /** * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} + * Guarantees: immutable; is valid as declared in + * {@link #isValidAddress(String)} */ public class Address { @@ -46,7 +47,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check + && value.equals(((Address) other).value)); // state check } @Override @@ -54,4 +55,7 @@ public int hashCode() { return value.hashCode(); } + public String toFormattedString() { + return String.format("Address: %s;", value); + } } diff --git a/src/main/java/seedu/address/model/person/BasePerson.java b/src/main/java/seedu/address/model/person/BasePerson.java new file mode 100644 index 00000000000..0b0bc592fda --- /dev/null +++ b/src/main/java/seedu/address/model/person/BasePerson.java @@ -0,0 +1,86 @@ +package seedu.address.model.person; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.model.category.Category; + +/** + * Represents a Base Person in the database with only name, phone and email + * fields. + */ +public abstract class BasePerson { + + private final Name name; + private final Phone phone; + private final Email email; + + /** + * Initialise name, phone and email for person + * + * @param n name + * @param p phone + * @param e email + */ + public BasePerson(Name n, Phone p, Email e) { + requireAllNonNull(n, p, e); + name = n; + phone = p; + email = e; + } + + public abstract Category getCategory(); + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + @Override + public String toString() { + return String.format( + "%s %s %s", + getName().toFormattedString(), + getPhone().toFormattedString(), + getEmail().toFormattedString()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof BasePerson)) { + return false; + } + + BasePerson otherBasePerson = (BasePerson) other; + + return otherBasePerson.getName().equals(getName()) + && otherBasePerson.getPhone().equals(getPhone()) + && otherBasePerson.getEmail().equals(getEmail()); + } + + public boolean isNurse() { + return false; + } + + public boolean isPatient() { + return false; + } + + public boolean isNextOfKin() { + return false; + } + + public boolean isPhysician() { + return false; + } +} diff --git a/src/main/java/seedu/address/model/person/Date.java b/src/main/java/seedu/address/model/person/Date.java new file mode 100644 index 00000000000..51d3c1eb00f --- /dev/null +++ b/src/main/java/seedu/address/model/person/Date.java @@ -0,0 +1,114 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.Month; +import java.time.format.DateTimeFormatter; + +/** + * Represents a date (can be nurse's unavailable date or fully-scheduled date). + * Guarantees: immutable; + * Valid as declared in {@link #isValidDateFormat(String)} and + * {@link #isValidDate(String)} + */ +public class Date implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Date should be in YYYY-MM-DD." + + " For example, 2022-11-11"; + public static final String MESSAGE_CONSTRAINTS_VALID_DATE = "Please input a valid date for the month" + + " For example, 2022-02-28 \n" + + "January, March, May, July, August, October, December have 31 days \n" + + "April, June, September, November have 30 days \n" + + "February has 28 or 29 days depending if it is a leap year."; + + public static final String VALIDATION_REGEX = "(20[0-9][0-9])-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"; + + public final LocalDate date; + private final String dateInString; + + /** + * Constructs a {@code Date}. + * + * @param date A valid date. + */ + public Date(String date) { + requireNonNull(date); + checkArgument(isValidDateFormat(date), MESSAGE_CONSTRAINTS); + checkArgument(isValidDate(date), MESSAGE_CONSTRAINTS_VALID_DATE); + this.date = LocalDate.parse(date); + this.dateInString = date; + } + + /** + * Constructs a {@code Date}. + */ + public Date(LocalDate date) { + requireNonNull(date); + this.date = date; + this.dateInString = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + /** + * Returns true if a given string is a valid date format. + */ + public static boolean isValidDateFormat(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String fullDate) { + String[] dateArr = fullDate.split("-"); // splits to [year,month,date] eg.["2022","01","31"] + int year = Integer.parseInt(dateArr[0]); + int monthInInt = Integer.parseInt(dateArr[1]); + Month month = Month.of(monthInInt); + int date = Integer.parseInt(dateArr[2]); // date + boolean isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + return date <= month.length(isLeapYear); + } + + /** + * Returns true if a given year is a leap year. + */ + public boolean isLeapYear(int year) { + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + } + + public String getString() { + return dateInString; + } + + @Override + public String toString() { + return date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Date // instanceof handles nulls + && date.equals(((Date) other).date)); // state check + } + + @Override + public int hashCode() { + return date.hashCode(); + } + + public LocalDate getDate() { + return date; + } + + public static Date today() { + return new Date(LocalDate.now()); + } + + @Override + public int compareTo(Date o) { + return date.compareTo(o.getDate()); + } + +} diff --git a/src/main/java/seedu/address/model/person/DateSlot.java b/src/main/java/seedu/address/model/person/DateSlot.java new file mode 100644 index 00000000000..b12e620ab3c --- /dev/null +++ b/src/main/java/seedu/address/model/person/DateSlot.java @@ -0,0 +1,261 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents a Patient's home-visit's date and time slot. + * Guarantees: immutable; is valid as declared in + * {@link #isValidDateSlot(String)} + */ +public class DateSlot implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Date and slot should be in YYYY-MM-DD,SLOT_NUMBER.\n" + + "The slot number can only be from 1 to 4. Slot 1 is 10am, slot 2 is 12pm, " + + "slot 3 is 2pm and slot 4 is 4pm.\n" + "For example, 2022-11-11,1"; + + /** + * The DateSlot can only be in YYYY-MM-DD,SLOT_NUMBER format without any space. + */ + // @@author xhphoong-reused + // Reused from + // https://mkyong.com/regular-expressions/how-to-validate-date-with-regular-expression/ + public static final String VALIDATION_REGEX = "((?:19|20)[0-9][0-9])-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])" + + "," + "([1-4])"; + // @@author + + public static final String SUCCESS_VISIT_CHECK = "V"; + public static final String FAIL_VISIT_CHECK = "FV"; + public static final String SUCCESS_ASSIGNED_CHECK = "A"; + private static final String SLOT_ONE = "10:00:00"; + private static final String SLOT_TWO = "12:00:00"; + private static final String SLOT_THREE = "14:00:00"; + private static final String SLOT_FOUR = "16:00:00"; + private static final Boolean DEFAULT_BOOLEAN = false; + private static final Long DEFAULT_EMPTY_ASSIGNED_NURSE = -1L; // No nurse assigned + private static final String DEFAULT_CHECK = " "; + public final LocalDateTime dateSlotTime; + private final String dateSlotInString; + private Boolean hasVisited = DEFAULT_BOOLEAN; + private Boolean hasAssigned = DEFAULT_BOOLEAN; + private Boolean isSuccessVisit = DEFAULT_BOOLEAN; + private Long nurseUidNo = DEFAULT_EMPTY_ASSIGNED_NURSE; + + /** + * Constructs a {@code DateSlot}. + * + * @param dateSlot A valid dateSlot. + */ + public DateSlot(String dateSlot) { + requireNonNull(dateSlot); + checkArgument(isValidDateSlot(dateSlot), MESSAGE_CONSTRAINTS); + this.dateSlotTime = parseDateSlot(dateSlot); + this.dateSlotInString = dateSlot; + this.hasVisited = DEFAULT_BOOLEAN; + this.hasAssigned = DEFAULT_BOOLEAN; + this.isSuccessVisit = DEFAULT_BOOLEAN; + this.nurseUidNo = DEFAULT_EMPTY_ASSIGNED_NURSE; + if (this.hasVisited == false) { + checkDateTime(); + } + } + + /** + * Constructs a {@code DateSlot}. + */ + public DateSlot(String dateSlot, Boolean isAssigned, Boolean isVisited, Boolean isSuccessfulVisit, + Long nurseUidNo) { + requireAllNonNull(dateSlot, isAssigned, isVisited, isSuccessfulVisit, nurseUidNo); + this.dateSlotTime = parseDateSlot(dateSlot); + this.dateSlotInString = dateSlot; + this.hasVisited = isVisited; + this.hasAssigned = isAssigned; + this.isSuccessVisit = isSuccessfulVisit; + this.nurseUidNo = nurseUidNo; + if (hasVisited == false) { + checkDateTime(); + } + } + + private static LocalDateTime parseDateSlot(String dateSlot) { + String[] s = dateSlot.split(","); + String date = s[0]; + int slotNumber = Integer.parseInt(s[1]); + String time = "T"; + + if (slotNumber == 1) { + time = time + SLOT_ONE; + } else if (slotNumber == 2) { + time = time + SLOT_TWO; + } else if (slotNumber == 3) { + time = time + SLOT_THREE; + } else if (slotNumber == 4) { + time = time + SLOT_FOUR; + } + + String dateTime = date + time; + return LocalDateTime.parse(dateTime); + } + + /** + * Returns true if a given string is a valid date and slot. + */ + public static boolean isValidDateSlot(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getString() { + return getAssignCheck() + ":" + getVisitCheck() + ":" + + this.dateSlotInString + ":" + nurseUidNo; + } + + /** + * Check the datetime of the DateSlot with the current datetime from the system + * clock. + * Mark isVisited true if the datetime is before the current datetime. + */ + public void checkDateTime() { + LocalDateTime currentDateTime = LocalDateTime.now(); + if (this.dateSlotTime.isBefore(currentDateTime)) { + this.hasVisited = true; + this.isSuccessVisit = true; + } + } + + /** + * Mark DateSlot as assigned with the assigned nurse uid. + */ + public void mark(Long nurseUidNo) { + this.hasAssigned = true; + this.nurseUidNo = nurseUidNo; + } + + /** + * Unmark DateSlot (not assigned) and remove assigned nurse uid. + */ + public void unmark() { + this.hasAssigned = false; + this.nurseUidNo = DEFAULT_EMPTY_ASSIGNED_NURSE; + } + + /** + * Mark DateSlot as fail to visit. + */ + public void markFail() { + this.isSuccessVisit = false; + } + + /** + * Mark DateSlot as success to visit. + */ + public void markSuccess() { + this.isSuccessVisit = true; + } + + private String getAssignCheck() { + String assignCheck = DEFAULT_CHECK; + + if (this.hasAssigned) { + assignCheck = SUCCESS_ASSIGNED_CHECK; + } + + return assignCheck; + } + + private String getVisitCheck() { + String visitCheck = DEFAULT_CHECK; + + if (this.hasVisited && this.isSuccessVisit) { + visitCheck = SUCCESS_VISIT_CHECK; + + } else if (this.hasVisited && !this.isSuccessVisit) { + visitCheck = FAIL_VISIT_CHECK; + + } + + return visitCheck; + } + + public String getDateSlotInString() { + return dateSlotInString; + } + + public String getDateSlotFormatted() { + return dateSlotTime.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + } + + public Boolean getHasVisited() { + return hasVisited; + } + + public Boolean getHasAssigned() { + return hasAssigned; + } + + public Boolean getIsSuccessVisit() { + return isSuccessVisit; + } + + public Long getNurseUidNo() { + return nurseUidNo; + } + + public LocalDateTime getDateTime() { + return dateSlotTime; + } + + public LocalDate getDate() { + String[] s = dateSlotInString.split(","); + String date = s[0]; + return LocalDate.parse(date); + } + + @Override + public String toString() { + return String.format( + "[%s] [%s] %s", + getAssignCheck(), + getVisitCheck(), + dateSlotTime.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateSlot // instanceof handles nulls + && dateSlotTime.equals(((DateSlot) other).dateSlotTime) + && hasVisited.equals(((DateSlot) other).hasVisited) + && hasAssigned.equals(((DateSlot) other).hasAssigned) + && isSuccessVisit.equals(((DateSlot) other).isSuccessVisit) + && nurseUidNo.equals(((DateSlot) other).nurseUidNo)); // state check + } + + /** + * Clone a dateslot. + * @return a new dateSlot + */ + public DateSlot clone() { + String dateSlotInString = this.getDateSlotInString(); + Boolean hasVisited = this.getHasVisited(); + Boolean hasAssigned = this.getHasAssigned(); + Boolean isSuccessVisit = this.getIsSuccessVisit(); + Long nurseUidNo = this.getNurseUidNo(); + return new DateSlot(dateSlotInString, hasAssigned, hasVisited, isSuccessVisit, nurseUidNo); + } + + @Override + public int hashCode() { + return dateSlotTime.hashCode(); + } + + @Override + public int compareTo(DateSlot o) { + return dateSlotTime.compareTo(o.getDateTime()); + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..4c4690db5ac 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -60,7 +60,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check + && value.equals(((Email) other).value)); // state check } @Override @@ -68,4 +68,8 @@ public int hashCode() { return value.hashCode(); } + public String toFormattedString() { + return String.format("Email: %s;", value); + } + } diff --git a/src/main/java/seedu/address/model/person/Gender.java b/src/main/java/seedu/address/model/person/Gender.java new file mode 100644 index 00000000000..13ad3517032 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Gender.java @@ -0,0 +1,96 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a Person's gender in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)} + */ +public class Gender { + + public static final String MESSAGE_CONSTRAINTS = "Gender can only be F or M, F for female, M for male"; + public static final String FEMALE_SYMBOL = "F"; + public static final String MALE_SYMBOL = "M"; + /** + * The gender can only be F or M. + */ + public static final String VALIDATION_REGEX = "[" + FEMALE_SYMBOL + "|" + MALE_SYMBOL + "]"; + private static final ArrayList COMMON_FEMALE_MISSPELLINGS = new ArrayList<>( + List.of("female", "females", "f")); + private static final ArrayList COMMON_MALE_MISSPELLINGS = new ArrayList<>( + List.of("male", "males", "m")); + public final String gender; + + /** + * Constructs a {@code Gender}. + * + * @param gender A valid gender. + */ + public Gender(String gender) { + requireNonNull(gender); + checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS); + this.gender = gender; + } + + /** + * Catches and formats any common misspellings as defined in the common + * misspelling constant of females and males + * + * @param test The String to be tested + * @return The female or male symbol if it is a misspelling and the original + * text otherwise + */ + public static String formatMisspelling(String test) { + if (COMMON_FEMALE_MISSPELLINGS.contains(test.trim().toLowerCase())) { + return FEMALE_SYMBOL; + } + if (COMMON_MALE_MISSPELLINGS.contains(test.trim().toLowerCase())) { + return MALE_SYMBOL; + } + return test; + } + + /** + * Returns true if a given string is a valid gender. + */ + public static boolean isValidGender(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return gender; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Gender // instanceof handles nulls + && gender.equals(((Gender) other).gender)); // state check + } + + @Override + public int hashCode() { + return gender.hashCode(); + } + + /** + * Checks if the two gender objects are equal, ignoring case. + * + * @param other other object to be checked against + * @return true if both genders are the same, false otherwise + */ + public boolean equalsIgnoreCase(Object other) { + return other == this + || (other instanceof Gender) + && gender.equalsIgnoreCase(((Gender) other).gender); + } + + public String toFormattedString() { + return String.format("Gender: %s;", gender); + } +} diff --git a/src/main/java/seedu/address/model/person/HomeVisit.java b/src/main/java/seedu/address/model/person/HomeVisit.java new file mode 100644 index 00000000000..65e21836494 --- /dev/null +++ b/src/main/java/seedu/address/model/person/HomeVisit.java @@ -0,0 +1,104 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +/** + * Represents a Nurse's home-visit. + * Guarantees: immutable; is valid as declared in + * {@link #isValidHomeVisit(String)} + */ +public class HomeVisit implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = "Home Visit should be in YYYY-MM-DD,SLOT_NUMBER:" + + "PATIENT_UID_NUMBER.\n" + "For example, 2022-11-11,1:2"; + + /** + * The DateSlot can only be in YYYY-MM-DD,SLOT_NUMBER:PATIENT NUMBER format + * without any space. + */ + // @@author xhphoong-reused + // Reused from + // https://mkyong.com/regular-expressions/how-to-validate-date-with-regular-expression/ + public static final String VALIDATION_REGEX = "((?:19|20)[0-9][0-9])-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])" + + "," + "([1-4])" + ":" + "\\d{1,18}"; + // @@author + + public final DateSlot homeVisitDateSlot; + public final Long homeVisitPatientUidNo; + + /** + * Constructs a {@code HomeVisit}. + * + * @param dateSlot A valid dateSlot, uid A valid uidNo. + */ + public HomeVisit(DateSlot dateSlot, Long uid) { + requireAllNonNull(uid, dateSlot); + this.homeVisitDateSlot = dateSlot; + this.homeVisitPatientUidNo = uid; + } + + /** + * Constructs a {@code HomeVisit}. + */ + public HomeVisit(String dateSlotAndUid) { + requireNonNull(dateSlotAndUid); + String[] s = dateSlotAndUid.split(":"); + this.homeVisitDateSlot = new DateSlot(s[0]); + this.homeVisitPatientUidNo = Long.parseLong(s[1]); + } + + /** + * Returns true if a given string is a valid home visit. + */ + public static boolean isValidHomeVisit(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getString() { + return homeVisitDateSlot.getDateSlotInString() + ":" + this.homeVisitPatientUidNo; + } + + @Override + public String toString() { + return homeVisitDateSlot.getDateSlotFormatted() + " : [UID] " + this.homeVisitPatientUidNo; + } + + public DateSlot getDateSlot() { + return homeVisitDateSlot; + } + + public Long getHomeVisitPatientUidNo() { + return homeVisitPatientUidNo; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HomeVisit // instanceof handles nulls + && homeVisitDateSlot.equals(((HomeVisit) other).homeVisitDateSlot) + && homeVisitPatientUidNo.equals(((HomeVisit) other).homeVisitPatientUidNo)); // state check + } + + /** + * Clone a homeVisit. + * + * @return a new homeVisit + */ + public HomeVisit clone() { + DateSlot dateSlot = this.getDateSlot(); + Long patientUidNo = this.getHomeVisitPatientUidNo(); + return new HomeVisit(dateSlot, patientUidNo); + } + + @Override + public int hashCode() { + return homeVisitDateSlot.hashCode() + homeVisitPatientUidNo.hashCode(); + } + + @Override + public int compareTo(HomeVisit o) { + return homeVisitDateSlot.compareTo(o.homeVisitDateSlot); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..2a6d06e33fe 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,10 +10,10 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - /* - * The first character of the address must not be a whitespace, + /** + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; @@ -38,7 +38,6 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } - @Override public String toString() { return fullName; @@ -48,7 +47,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.equals(((Name) other).fullName)); // state check } @Override @@ -56,4 +55,8 @@ public int hashCode() { return fullName.hashCode(); } + public String toFormattedString() { + return String.format("Name: %s;", fullName); + } + } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index c9b5868427c..8b34fe6f505 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -25,7 +25,7 @@ public boolean test(Person person) { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check + && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check } } diff --git a/src/main/java/seedu/address/model/person/NextOfKin.java b/src/main/java/seedu/address/model/person/NextOfKin.java new file mode 100644 index 00000000000..c7dad20a1cb --- /dev/null +++ b/src/main/java/seedu/address/model/person/NextOfKin.java @@ -0,0 +1,29 @@ +package seedu.address.model.person; + +import seedu.address.model.category.Category; + +/** + * Represents Next of Kin for patient + */ +public class NextOfKin extends BasePerson { + + /** + * Initialise next of kin user with name, phone and email. + * + * @param n name + * @param p phone + * @param e email + */ + public NextOfKin(Name n, Phone p, Email e) { + super(n, p, e); + } + + @Override + public Category getCategory() { + return new Category(Category.NEXTOFKIN_SYMBOL); + } + + public boolean isNextOfKin() { + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/Nurse.java b/src/main/java/seedu/address/model/person/Nurse.java new file mode 100644 index 00000000000..469553f4a03 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Nurse.java @@ -0,0 +1,129 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.address.model.category.Category; +import seedu.address.model.tag.Tag; + +/** + * Represents a nurse that home-visit patients in Healthcare Xpress book. + */ + +public class Nurse extends Person { + + private static final String MESSAGE_FOR_EMPTY_HOME_VISIT_LIST = "No home visit assigned yet."; + private static final String MESSAGE_FOR_EMPTY_UNAVAILABLE_DATE = "No unavailable date."; + private final List homeVisitList = new ArrayList<>(); + private final List unavailableDateList = new ArrayList<>(); + private final List fullyScheduledDateList = new ArrayList<>(); + + /** + * Every field must be present and not null. + */ + public Nurse(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, Set tags, + List unavailableDates, List homeVisits, List fullyScheduledDates) { + super(uid, name, gender, phone, email, address, tags); + this.unavailableDateList.addAll(unavailableDates); + this.homeVisitList.addAll(homeVisits); + this.fullyScheduledDateList.addAll(fullyScheduledDates); + } + + /** + * Nurse Constructor used when first creating the nurse object + * + * @param uid The nurse's uid + * @param name The nurse's name + * @param gender The nurse's gender + * @param phone The nurse's phone number + * @param email The nurse's email + * @param address The nurse's address + * @param tags The nurse's tags + * @param unavailableDates The nurse's unavailable dates + */ + public Nurse(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, Set tags, + List unavailableDates) { + super(uid, name, gender, phone, email, address, tags); + this.unavailableDateList.addAll(unavailableDates); + } + + public Category getCategory() { + return new Category("N"); + } + + public String getCategoryIndicator() { + return PersonType.NURSE.toString(); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return super.hashCode() + Objects.hash(unavailableDateList) + Objects.hash(homeVisitList) + + Objects.hash(fullyScheduledDateList); + } + + /** + * Returns a sorted home visit list + */ + public List getHomeVisits() { + Collections.sort(homeVisitList); + return homeVisitList; + } + + /** + * Returns a sorted unavailable dates + */ + public List getUnavailableDates() { + Collections.sort(unavailableDateList); + return unavailableDateList; + } + + /** + * Returns a sorted fully scheduled dates + */ + public List getFullyScheduledDates() { + Collections.sort(fullyScheduledDateList); + return fullyScheduledDateList; + } + + public String getHomesVisitsInString() { + String homeVisitsString = getHomeVisits().stream() + .map(x -> x.toString()).collect(Collectors.joining(", ")); + if (homeVisitsString.length() == 0) { + return MESSAGE_FOR_EMPTY_HOME_VISIT_LIST; + } + return String.format("Home Visits: %s;", homeVisitsString); + } + + public String getUnavailableDatesInString() { + String unavailableString = getUnavailableDates().stream() + .map(x -> x.toString()).collect(Collectors.joining(", ")); + if (unavailableString.length() == 0) { + return MESSAGE_FOR_EMPTY_UNAVAILABLE_DATE; + } + return String.format("Unavailable Dates: %s;", unavailableString); + } + + @Override + public String toString() { + return String.format( + "%s %s %s %s", + getCategory().toFormattedString(), + super.toString(), + getUnavailableDatesInString(), + getHomesVisitsInString()); + } + + public boolean isNurse() { + return true; + } + + public String toLiteString() { + return String.format("Nurse [Uid:%s]", getUid()); + } + +} diff --git a/src/main/java/seedu/address/model/person/Patient.java b/src/main/java/seedu/address/model/person/Patient.java new file mode 100644 index 00000000000..6c4f33db3ed --- /dev/null +++ b/src/main/java/seedu/address/model/person/Patient.java @@ -0,0 +1,136 @@ +package seedu.address.model.person; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.address.model.category.Category; +import seedu.address.model.tag.Tag; + +/** + * Represents a patient that requires home-visit in Healthcare Xpress book. + */ +public class Patient extends Person { + + private static final String MESSAGE_FOR_EMPTY_DATESLOT = "Home Visit date and slot has not been set yet."; + private static final String NO_NEXTOFKIN_SET = "No next of kin info was added for this patient."; + private static final String NO_PHYSICIAN_SET = "There is currently no attending physician for this patient."; + public final List dateSlots = new ArrayList<>(); + private final Optional attendingPhysician; + private final Optional nextOfKin; + + /** + * Initialise patient with no attending physician and no next of kin. + */ + public Patient(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, + Set tags, List dateTimeSlot) { + super(uid, name, gender, phone, email, address, tags); + requireAllNonNull(dateTimeSlot); + dateSlots.addAll(dateTimeSlot); + attendingPhysician = Optional.empty(); + nextOfKin = Optional.empty(); + + } + + /** + * Every field, except attending physician and next of kin, must be present and + * not null. + */ + public Patient(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, + Set tags, List dateTime, Physician p, NextOfKin n) { + super(uid, name, gender, phone, email, address, tags); + requireAllNonNull(dateTime); + dateSlots.addAll(dateTime); + attendingPhysician = Optional.ofNullable(p); + nextOfKin = Optional.ofNullable(n); + } + + /** + * Initialise patient with given attending physician and next of kin. + */ + public Patient(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, + Set tags, List dateSlot, Optional p, Optional n) { + super(uid, name, gender, phone, email, address, tags); + requireAllNonNull(dateSlot); + dateSlots.addAll(dateSlot); + attendingPhysician = p; + nextOfKin = n; + } + + public Optional getAttendingPhysician() { + return attendingPhysician; + } + + public Optional getNextOfKin() { + return nextOfKin; + } + + public String getNextOfKinDetails() { + String[] output = new String[] { NO_NEXTOFKIN_SET }; + nextOfKin.ifPresent(x -> output[0] = "NOK: " + x); + return output[0]; + } + + public String getPhysicianDetails() { + String[] output = new String[] { NO_PHYSICIAN_SET }; + attendingPhysician.ifPresent(x -> output[0] = x.toString()); + return output[0]; + } + + public Category getCategory() { + return new Category(Category.PATIENT_SYMBOL); + } + + public String getCategoryIndicator() { + return PersonType.PATIENT.toString(); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return super.hashCode() + Objects.hash(dateSlots); + } + + /** + * Returns a sorted date and slot list + */ + public List getDatesSlots() { + Collections.sort(dateSlots); + return dateSlots; + } + + public String getDatesSlotsInString() { + String dateSlotsString = getDatesSlots().stream().map(x -> x.toString()).collect( + Collectors.joining(", ")); + if (dateSlotsString.length() == 0) { + return String.format("Home Visits Date and Time: %s;", MESSAGE_FOR_EMPTY_DATESLOT); + } + return String.format("Home Visits Date and Time: %s;", dateSlotsString); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getCategory().toFormattedString()) + .append(" ") + .append(super.toString()) + .append(" ") + .append(getDatesSlotsInString()); + + return builder.toString(); + } + + public String toLiteString() { + return String.format("Patient [Uid:%s]", this.getUid()); + } + + public boolean isPatient() { + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..cb89d706f56 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -7,53 +7,69 @@ import java.util.Objects; import java.util.Set; +import seedu.address.model.category.Category; 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 { +public class Person extends BasePerson { // Identity fields - private final Name name; - private final Phone phone; - private final Email email; + private final Uid uid; + private final Gender gender; // Data fields private final Address address; private final Set tags = new HashSet<>(); + // Check fields + private final int similarityThreshold = 5; + /** * 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); - this.name = name; - this.phone = phone; - this.email = email; + public Person(Uid uid, Name name, Gender gender, Phone phone, Email email, Address address, Set tags) { + super(name, phone, email); + requireAllNonNull(address, tags); + if (Objects.isNull(uid)) { + this.uid = new Uid(); + } else { + this.uid = uid; + } + this.gender = gender; this.address = address; this.tags.addAll(tags); } - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; + /** + * @return the id + */ + public Uid getUid() { + return uid; } - public Email getEmail() { - return email; + public Gender getGender() { + return gender; } public Address getAddress() { return address; } + public Category getCategory() { + return null; + } + + public String getCategoryIndicator() { + return "person"; + } + /** - * 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() { @@ -69,8 +85,7 @@ public boolean isSamePerson(Person otherPerson) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + return otherPerson != null && otherPerson.getUid().equals(getUid()); } /** @@ -88,7 +103,10 @@ public boolean equals(Object other) { } Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) + + return otherPerson.getUid().equals(getUid()) + && otherPerson.getName().equals(getName()) + && otherPerson.getGender().equals(getGender()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) @@ -98,19 +116,19 @@ public boolean equals(Object other) { @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(uid, getName(), gender, getPhone(), getEmail(), address, tags); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); + builder.append(getUid().toFormattedString()) + .append(" ") + .append(super.toString()) + .append(" ") + .append(getGender().toFormattedString()) + .append(" ") + .append(getAddress().toFormattedString()); Set tags = getTags(); if (!tags.isEmpty()) { @@ -120,4 +138,38 @@ public String toString() { return builder.toString(); } + /** + * Returns true if both persons are similar to each other. + */ + public boolean isSimilarPerson(Person otherPerson) { + int counter = 0; + if (otherPerson == this) { + return true; + } + + if (otherPerson.getName().equals(getName())) { + counter++; + } + + if (otherPerson.getGender().equals(getGender())) { + counter++; + } + + if (otherPerson.getPhone().equals(getPhone())) { + counter++; + } + + if (otherPerson.getEmail().equals(getEmail())) { + counter++; + } + + if (otherPerson.getAddress().equals(getAddress())) { + counter++; + } + + if (otherPerson.getTags().equals(getTags())) { + counter++; + } + return counter >= similarityThreshold; + } } diff --git a/src/main/java/seedu/address/model/person/PersonType.java b/src/main/java/seedu/address/model/person/PersonType.java new file mode 100644 index 00000000000..d1cb3bdbe1d --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonType.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +/** + * Enum to represent the type of person someone is + */ +public enum PersonType { + PATIENT("Patient"), NURSE("Nurse"); + + private final String personTypeName; + + PersonType(String name) { + this.personTypeName = name; + } + + @Override + public String toString() { + return personTypeName; + } + +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..5c201fbd66a 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -9,9 +9,8 @@ */ public class Phone { - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; + public static final String MESSAGE_CONSTRAINTS = "Phone numbers should " + + "only contain numbers, and it should be at least 3 digits long"; public static final String VALIDATION_REGEX = "\\d{3,}"; public final String value; @@ -42,7 +41,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check + && value.equals(((Phone) other).value)); // state check } @Override @@ -50,4 +49,8 @@ public int hashCode() { return value.hashCode(); } + public String toFormattedString() { + return String.format("Phone: %s;", value); + } + } diff --git a/src/main/java/seedu/address/model/person/Physician.java b/src/main/java/seedu/address/model/person/Physician.java new file mode 100644 index 00000000000..5b292485fbb --- /dev/null +++ b/src/main/java/seedu/address/model/person/Physician.java @@ -0,0 +1,27 @@ +package seedu.address.model.person; + +import seedu.address.model.category.Category; + +/** + * Represents a physician in the database. + */ +public class Physician extends BasePerson { + + public Physician(Name n, Phone p, Email e) { + super(new Name("Dr " + n.fullName), p, e); + } + + @Override + public Category getCategory() { + return new Category(Category.PHYSICIAN_SYMBOL); + } + + @Override + public String toString() { + return "Attending Physician: " + super.toString(); + } + + public boolean isPhysician() { + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/Uid.java b/src/main/java/seedu/address/model/person/Uid.java new file mode 100644 index 00000000000..b2a0f89cb17 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Uid.java @@ -0,0 +1,119 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Represents a Person's Uid in the records system. + * Guarantees: immutable; is valid as declared in {@link #isValidUid(String)} + */ +public class Uid implements Comparable { + public static final String MESSAGE_CONSTRAINTS = "Id should only contain one positive numeric character," + + " and it should not be blank and below 99998!"; + + /** + * The first character of the id must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "^[0-9]*$"; + private static final AtomicLong NEXT_UID = new AtomicLong(0); + private static final Long UNIVERSAL_UID = 99999L; + + public final Long uid; + + /** + * Constructor for Uid using a String as an input + * + * @param uid + */ + public Uid(String uid) { + requireNonNull(uid); + long parsedUid = Long.parseLong(uid); + this.uid = parsedUid; + NEXT_UID.set(parsedUid + 1); + } + + /** + * Constructor for Uid using a Long as an input + * + * @param uid + */ + public Uid(Long uid) { + requireNonNull(uid); + this.uid = uid; + NEXT_UID.set(uid + 1); + } + + /** + * Constructor for Uid + */ + public Uid() { + this.uid = NEXT_UID.getAndIncrement(); + } + + /** + * Returns true if a given string is a valid id. + */ + public static boolean isValidUid(String test) { + requireNonNull(test); + try { + Long parsedUid = Long.parseLong(test); + return test.matches(VALIDATION_REGEX) && parsedUid < 99998L; + } catch (NumberFormatException e) { + return false; + } + } + + public static Uid generateUniversalUid() { + return new Uid(UNIVERSAL_UID); + } + + /** + * @return the id + */ + public Long getUid() { + return uid; + } + + @Override + public String toString() { + return uid.toString(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Uid)) { + return false; + } + + Uid otherUid = (Uid) other; + + if (this.uid.equals(UNIVERSAL_UID) || otherUid.uid.equals(UNIVERSAL_UID)) { + return true; + } + + // state check + return uid.equals(otherUid.uid); + } + + @Override + public int hashCode() { + return uid.hashCode(); + } + + @Override + public int compareTo(Uid o) { + return uid.compareTo(o.getUid()); + } + + public String toFormattedString() { + return String.format("Uid: %s;", uid.toString()); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..2cc69d1ada6 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -5,6 +5,7 @@ import java.util.Iterator; import java.util.List; +import java.util.Optional; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -12,12 +13,16 @@ import seedu.address.model.person.exceptions.PersonNotFoundException; /** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so + * A list of persons that enforces uniqueness between its elements and does not + * allow nulls. + * A person is considered unique by comparing using + * {@code Person#isSamePerson(Person)}. As such, adding and updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that + * the person being added or updated is + * unique in terms of identity in the UniquePersonList. However, the removal of + * a person uses Person#equals(Object) so * as to ensure that the person with exactly the same fields will be removed. - * + *

* Supports a minimal set of list operations. * * @see Person#isSamePerson(Person) @@ -25,8 +30,8 @@ public class UniquePersonList implements Iterable { private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); + private final ObservableList internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); /** * Returns true if the list contains an equivalent person as the given argument. @@ -36,6 +41,22 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains an similar person as the given argument. + */ + public boolean containsSimilar(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSimilarPerson); + } + + /** + * Returns an optional that contains an similar person as the given argument. + */ + public Optional findSimilarPerson(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().filter(toCheck::isSimilarPerson).findFirst(); + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -51,7 +72,8 @@ public void add(Person toAdd) { /** * Replaces the person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the list. */ public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java index d7290f59442..4c7648a1c70 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java @@ -1,7 +1,8 @@ package seedu.address.model.person.exceptions; /** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * Signals that the operation will result in duplicate Persons (Persons are + * considered duplicates if they have the same * identity). */ public class DuplicatePersonException extends RuntimeException { diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..588cfb5fbf8 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java @@ -3,4 +3,5 @@ /** * Signals that the operation is unable to find the specified person. */ -public class PersonNotFoundException extends RuntimeException {} +public class PersonNotFoundException 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 b0ea7e7dad7..7b462811b42 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -5,7 +5,8 @@ /** * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} + * Guarantees: immutable; name is valid as declared in + * {@link #isValidTagName(String)} */ public class Tag { @@ -36,7 +37,7 @@ public static boolean isValidTagName(String test) { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check + && tagName.equals(((Tag) other).tagName)); // state check } @Override diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..e9d3343e3a3 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,16 +1,25 @@ package seedu.address.model.util; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.HomeVisit; import seedu.address.model.person.Name; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -19,24 +28,37 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Nurse(new Uid(1L), new Name("Betsy"), new Gender("F"), new Phone("98345432"), + new Email("betsy@example.com"), + new Address("Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-01"), + getTagSet("Pediatric", "heartDiseaseSpecialist"), getUnavailableDateList("2022-11-11"), + getHomeVisitsList("2022-12-12,1:4"), getFullyScheduledDateList("2022-10-12")), + new Nurse(new Uid(2L), new Name("Jason"), new Gender("M"), new Phone("98723432"), + new Email("jason@example.com"), + new Address("Blk 855 Woodlands Street 83, Singapore 730855 #01-01"), + getTagSet("asthmaSpecialist"), getUnavailableDateList("2022-10-09"), + getHomeVisitsList("2022-10-10,2:5", "2022-12-24,1:6"), + getFullyScheduledDateList("2022-10-10")), + new Patient(new Uid(3L), new Name("Alex Yeoh"), new Gender("M"), new Phone("87438807"), + new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), + getTagSet("friends"), getDatesSlotsList("2022-11-11,1")), + new Patient(new Uid("4"), new Name("Bernice Yu"), new Gender("F"), new Phone("99272758"), + new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + getTagSet("colleagues", "friends"), getDatesSlotsList("2022-12-12,1")), + new Patient(new Uid("5"), new Name("Charlotte Oliveiro"), new Gender("F"), new Phone("93210283"), + new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + getTagSet("neighbours"), + getDatesSlotsList("2022-10-10,2", "2022-09-24,1")), + new Patient(new Uid("6"), new Name("David Li"), new Gender("M"), new Phone("91031282"), + new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + getTagSet("family"), getDatesSlotsList("2022-10-10,2", "2022-12-24,1")), + new Patient(new Uid("7"), new Name("Irfan Ibrahim"), new Gender("M"), new Phone("92492021"), + new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + getTagSet("classmates"), + getDatesSlotsList("2022-10-10,1", "2022-09-24,1", "2023-01-13,3")), + new Patient(new Uid("8"), new Name("Roy Balakrishnan"), new Gender("M"), new Phone("92624417"), + new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), + getTagSet("colleagues"), getDatesSlotsList("2022-12-12,4")) }; } @@ -57,4 +79,53 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a homeVisit list containing the list of strings given. + */ + public static List getHomeVisitsList(String... strings) { + return Arrays.stream(strings) + .map(HomeVisit::new) + .collect(Collectors.toList()); + } + + /** + * Returns an unavailable date list containing the list of strings given. + */ + public static List getUnavailableDateList(String... strings) { + return Arrays.stream(strings) + .map(Date::new) + .collect(Collectors.toList()); + } + + /** + * Returns a fully scheduled date list containing the list of strings given. + */ + public static List getFullyScheduledDateList(String... strings) { + return Arrays.stream(strings) + .map(Date::new) + .collect(Collectors.toList()); + } + + /** + * Returns a datesSlots list containing the list of strings given. + */ + public static List getDatesSlotsList(String... strings) { + return Arrays.stream(strings) + .map(DateSlot::new) + .collect(Collectors.toList()); + } + + /** + * Returns a assigned dateSlots list. + */ + public static List getAssignedDateSlotsList(List stringList) { + List toBeUpdateDateSlotList = new ArrayList<>(); + for (String s : stringList) { + String[] sArray = s.split(":"); + toBeUpdateDateSlotList.add(new DateSlot(sArray[0], true, false, + false, Long.parseLong(sArray[1]))); + } + return toBeUpdateDateSlotList; + } + } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index 4599182b3f9..a693dad3541 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -19,9 +19,12 @@ public interface AddressBookStorage { /** * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected + * format. + * @throws IOException if there was any problem when reading from + * the storage. */ Optional readAddressBook() throws DataConversionException, IOException; @@ -32,6 +35,7 @@ public interface AddressBookStorage { /** * Saves the given {@link ReadOnlyAddressBook} to the storage. + * * @param addressBook cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDate.java b/src/main/java/seedu/address/storage/JsonAdaptedDate.java new file mode 100644 index 00000000000..22f55a466c7 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDate.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Date; + +/** + * Jackson-friendly version of {@link Date}. + */ +class JsonAdaptedDate { + + private final String date; + + /** + * Constructs a {@code JsonAdaptedDate} with the given {@code Date}. + */ + @JsonCreator + public JsonAdaptedDate(String date) { + this.date = date; + } + + /** + * Converts a given {@code Date} into this class for Jackson use. + */ + public JsonAdaptedDate(Date source) { + date = source.getString(); + } + + @JsonValue + public String getDate() { + return date; + } + + /** + * Converts this Jackson-friendly adapted date object into the model's + * {@code Date} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted date. + */ + public Date toModelType() throws IllegalValueException { + if (!Date.isValidDateFormat(date)) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + return new Date(date); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDateSlot.java b/src/main/java/seedu/address/storage/JsonAdaptedDateSlot.java new file mode 100644 index 00000000000..f2bea158ec9 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDateSlot.java @@ -0,0 +1,73 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.DateSlot; + +/** + * Jackson-friendly version of {@link DateSlot}. + */ +class JsonAdaptedDateSlot { + private final String dateSlot; + private final Boolean isAssigned; + private final Boolean isVisited; + private final Boolean isSuccessVisit; + private final Long nurseUidNo; + private final String dateSlotInString; + + /** + * Constructs a {@code JsonAdaptedDateSlot} with the given {@code DateSlot}. + */ + @JsonCreator + public JsonAdaptedDateSlot(String dateSlot) { + this.dateSlot = dateSlot; + String[] s = dateSlot.split(":"); + this.isAssigned = s[0].equals(DateSlot.SUCCESS_ASSIGNED_CHECK); + if (s[1].equals(DateSlot.SUCCESS_VISIT_CHECK)) { + this.isVisited = true; + this.isSuccessVisit = true; + } else if (s[1].equals(DateSlot.FAIL_VISIT_CHECK)) { + this.isVisited = true; + this.isSuccessVisit = false; + } else { + this.isVisited = false; + this.isSuccessVisit = false; + } + this.dateSlotInString = s[2]; + this.nurseUidNo = Long.parseLong(s[3]); + } + + /** + * Converts a given {@code DateSlot} into this class for Jackson use. + */ + public JsonAdaptedDateSlot(DateSlot source) { + dateSlot = source.getString(); + dateSlotInString = source.getDateSlotInString(); + isAssigned = source.getHasAssigned(); + isVisited = source.getHasVisited(); + isSuccessVisit = source.getIsSuccessVisit(); + nurseUidNo = source.getNurseUidNo(); + } + + @JsonValue + public String getDateSlot() { + return dateSlot; + } + + /** + * Converts this Jackson-friendly adapted date slot object into the model's + * {@code DateSlot} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted date slot. + */ + public DateSlot toModelType() throws IllegalValueException { + if (!DateSlot.isValidDateSlot(dateSlotInString)) { + throw new IllegalValueException(DateSlot.MESSAGE_CONSTRAINTS); + } + return new DateSlot(dateSlotInString, isAssigned, isVisited, isSuccessVisit, nurseUidNo); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedHomeVisit.java b/src/main/java/seedu/address/storage/JsonAdaptedHomeVisit.java new file mode 100644 index 00000000000..aae8bc2261f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedHomeVisit.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.HomeVisit; + +/** + * Jackson-friendly version of {@link HomeVisit}. + */ +class JsonAdaptedHomeVisit { + + private final String homeVisit; + + /** + * Constructs a {@code JsonAdaptedHomeVisit} with the given {@code HomeVisit}. + */ + @JsonCreator + public JsonAdaptedHomeVisit(String homeVisit) { + this.homeVisit = homeVisit; + } + + /** + * Converts a given {@code HomeVisit} into this class for Jackson use. + */ + public JsonAdaptedHomeVisit(HomeVisit source) { + homeVisit = source.getString(); + } + + @JsonValue + public String getHomeVisit() { + return homeVisit; + } + + /** + * Converts this Jackson-friendly adapted homeVisit object into the model's + * {@code HomeVisit} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted home visit slot. + */ + public HomeVisit toModelType() throws IllegalValueException { + if (!HomeVisit.isValidHomeVisit(homeVisit)) { + throw new IllegalValueException(HomeVisit.MESSAGE_CONSTRAINTS); + } + return new HomeVisit(homeVisit); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..6fc28295a4a 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -1,20 +1,36 @@ package seedu.address.storage; +import static seedu.address.model.category.Category.NURSE_SYMBOL; +import static seedu.address.model.category.Category.PATIENT_SYMBOL; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.category.Category; import seedu.address.model.person.Address; +import seedu.address.model.person.Date; +import seedu.address.model.person.DateSlot; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.HomeVisit; import seedu.address.model.person.Name; +import seedu.address.model.person.NextOfKin; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Physician; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -23,87 +39,342 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - + public static final String NOT_APPLICABLE = "NA"; + private final Long uid; private final String name; + private final String category; + private final String gender; private final String phone; private final String email; private final String address; + private final List homeVisits = new ArrayList<>(); + private final List unavailableDates = new ArrayList<>(); + private final List fullyAssignedDates = new ArrayList<>(); + private final List dateSlots = new ArrayList<>(); private final List tagged = new ArrayList<>(); + private final String pName; + private final String pPhone; + private final String pEmail; + private final String nName; + private final String nPhone; + private final String nEmail; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + public JsonAdaptedPerson(@JsonProperty("uid") Long uid, @JsonProperty("name") String name, + @JsonProperty("category") String category, + @JsonProperty("gender") String gender, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("dateSlots") List dateSlot, + @JsonProperty("tagged") List tagged, + @JsonProperty("homeVisits") List homeVisit, + @JsonProperty("unavailableDates") List unavailableDateList, + @JsonProperty("fullyAssignedDates") List fullyAssignedDateList, + @JsonProperty("pName") String pName, + @JsonProperty("pPhone") String pPhone, + @JsonProperty("pEmail") String pEmail, + @JsonProperty("nName") String nName, + @JsonProperty("nPhone") String nPhone, + @JsonProperty("nEmail") String nEmail) { + + this.uid = uid; this.name = name; + this.category = category; + this.gender = gender; this.phone = phone; this.email = email; this.address = address; + + if (dateSlot != null) { + this.dateSlots.addAll(dateSlot); + } + this.pName = Objects.requireNonNullElse(pName, NOT_APPLICABLE); + this.pPhone = Objects.requireNonNullElse(pPhone, NOT_APPLICABLE); + this.pEmail = Objects.requireNonNullElse(pEmail, NOT_APPLICABLE); + this.nName = Objects.requireNonNullElse(nName, NOT_APPLICABLE); + this.nPhone = Objects.requireNonNullElse(nPhone, NOT_APPLICABLE); + this.nEmail = Objects.requireNonNullElse(nEmail, NOT_APPLICABLE); + if (tagged != null) { this.tagged.addAll(tagged); } + + if (homeVisits != null) { + this.homeVisits.addAll(homeVisit); + } + + if (unavailableDateList != null) { + this.unavailableDates.addAll(unavailableDateList); + } + + if (fullyAssignedDateList != null) { + this.fullyAssignedDates.addAll(fullyAssignedDateList); + } + } /** * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { + category = source.getCategory().categoryName; + boolean isPatient = category.equals(PATIENT_SYMBOL); + + if (isPatient) { + Patient sourcePatient = (Patient) source; + dateSlots.addAll(sourcePatient.getDatesSlots().stream().map(JsonAdaptedDateSlot::new) + .collect(Collectors.toList())); + pName = getpName(sourcePatient); + pEmail = getpEmail(sourcePatient); + pPhone = getpPhone(sourcePatient); + nName = getnName(sourcePatient); + nEmail = getnEmail(sourcePatient); + nPhone = getnPhone(sourcePatient); + } else { + homeVisits.addAll(((Nurse) source).getHomeVisits().stream().map(JsonAdaptedHomeVisit::new) + .collect(Collectors.toList())); + unavailableDates.addAll(((Nurse) source).getUnavailableDates().stream().map(JsonAdaptedDate::new) + .collect(Collectors.toList())); + fullyAssignedDates.addAll(((Nurse) source).getFullyScheduledDates().stream().map(JsonAdaptedDate::new) + .collect(Collectors.toList())); + pName = NOT_APPLICABLE; + pPhone = NOT_APPLICABLE; + pEmail = NOT_APPLICABLE; + nName = NOT_APPLICABLE; + nEmail = NOT_APPLICABLE; + nPhone = NOT_APPLICABLE; + } + + uid = source.getUid().uid; name = source.getName().fullName; + gender = source.getGender().gender; phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); + tagged.addAll(source.getTags().stream().map(JsonAdaptedTag::new).collect(Collectors.toList())); + } + + private String getpName(Patient sourcePatient) { + String[] physNameArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getAttendingPhysician().ifPresent(x -> physNameArr[0] = x.getName().fullName.substring(3)); + return physNameArr[0]; + } + + private String getpEmail(Patient sourcePatient) { + String[] physEmailArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getAttendingPhysician().ifPresent(x -> physEmailArr[0] = x.getEmail().value); + return physEmailArr[0]; + } + + private String getpPhone(Patient sourcePatient) { + String[] physPhoneArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getAttendingPhysician().ifPresent(x -> physPhoneArr[0] = x.getPhone().value); + return physPhoneArr[0]; + } + + private String getnName(Patient sourcePatient) { + String[] nokNameArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getNextOfKin().ifPresent(x -> nokNameArr[0] = x.getName().fullName); + return nokNameArr[0]; + } + + private String getnEmail(Patient sourcePatient) { + String[] nokEmailArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getNextOfKin().ifPresent(x -> nokEmailArr[0] = x.getEmail().value); + return nokEmailArr[0]; + } + + private String getnPhone(Patient sourcePatient) { + String[] nokPhoneArr = new String[] { NOT_APPLICABLE }; + sourcePatient.getNextOfKin().ifPresent(x -> nokPhoneArr[0] = x.getPhone().value); + return nokPhoneArr[0]; } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Jackson-friendly adapted person object into the model's + * {@code Person} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); + final Uid modelUid = getModelUid(); + final Name modelName = getModelName(name, false, false); + final Gender modelGender = getModelGender(); + final Phone modelPhone = getModelPhone(phone, false, false); + final Email modelEmail = getModelEmail(email, false, false); + final Address modelAddress = getModelAddress(); + final Set modelTags = getModelTags(); + + if (category == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Category.class.getSimpleName())); } - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + if (category.equals(NURSE_SYMBOL)) { + List modelUnavailableDates = getModelUnavailableDates(); + List modelHomeVisits = getModelHomeVisits(); + List modelFullySchedulledDates = getModelFullyScheduledDates(); + return new Nurse(modelUid, modelName, modelGender, modelPhone, modelEmail, modelAddress, modelTags, + modelUnavailableDates, modelHomeVisits, modelFullySchedulledDates); + } else if (category.equals(PATIENT_SYMBOL)) { + List modelDateSlots = getModelDateSlots(); + Optional modelPhysician = getModelPhysician(); + Optional modelNextOfKin = getModelNextOfKin(); + return new Patient(modelUid, modelName, modelGender, modelPhone, modelEmail, + modelAddress, modelTags, modelDateSlots, modelPhysician, modelNextOfKin); + + } + throw new IllegalValueException(Category.MESSAGE_CONSTRAINTS); + } + + private Uid getModelUid() throws IllegalValueException { + if (uid == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Id.class.getSimpleName())); + } + return new Uid(uid); + } + + private Name getModelName(String name, Boolean isPhysician, Boolean isNok) throws IllegalValueException { + String indicator = ""; + if (isPhysician) { + indicator = "Physician's "; + } + if (isNok) { + indicator = "Next Of Kin's "; + } + if (name == null || name.equals(NOT_APPLICABLE)) { + throw new IllegalValueException(indicator + + String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + throw new IllegalValueException(indicator + Name.MESSAGE_CONSTRAINTS); + } + return new Name(name); + } + + private Gender getModelGender() throws IllegalValueException { + if (gender == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName())); + } + if (!Gender.isValidGender(gender)) { + throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS); } - final Name modelName = new Name(name); + return new Gender(gender); + } + private Phone getModelPhone(String phone, Boolean isPhysician, Boolean isNok) throws IllegalValueException { + String indicator = ""; + if (isPhysician) { + indicator = "Physician's "; + } + if (isNok) { + indicator = "Next Of Kin's "; + } if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + throw new IllegalValueException(indicator + + String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + throw new IllegalValueException(indicator + Phone.MESSAGE_CONSTRAINTS); } - final Phone modelPhone = new Phone(phone); + return new Phone(phone); + } + private Email getModelEmail(String email, Boolean isPhysician, Boolean isNok) throws IllegalValueException { + String indicator = ""; + if (isPhysician) { + indicator = "Physician's "; + } + if (isNok) { + indicator = "Next Of Kin's "; + } if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + throw new IllegalValueException(indicator + + String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); } if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + throw new IllegalValueException(indicator + Email.MESSAGE_CONSTRAINTS); } - final Email modelEmail = new Email(email); + return new Email(email); + } + private Address getModelAddress() throws IllegalValueException { if (address == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); } if (!Address.isValidAddress(address)) { throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); } - final Address modelAddress = new Address(address); + return new Address(address); + } - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + private Set getModelTags() throws IllegalValueException { + final List personTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tagged) { + personTags.add(tag.toModelType()); + } + return new HashSet<>(personTags); + } + + private List getModelHomeVisits() throws IllegalValueException { + final List nurseHomeVisitList = new ArrayList<>(); + for (JsonAdaptedHomeVisit homeVisit : homeVisits) { + nurseHomeVisitList.add(homeVisit.toModelType()); + } + return nurseHomeVisitList; + } + + private List getModelUnavailableDates() throws IllegalValueException { + final List nurseUnavailableDate = new ArrayList<>(); + for (JsonAdaptedDate date : unavailableDates) { + nurseUnavailableDate.add(date.toModelType()); + } + return nurseUnavailableDate; + } + + private List getModelFullyScheduledDates() throws IllegalValueException { + final List nurseFullySchedulledDates = new ArrayList<>(); + for (JsonAdaptedDate date : fullyAssignedDates) { + nurseFullySchedulledDates.add(date.toModelType()); + } + return nurseFullySchedulledDates; + } + + private List getModelDateSlots() throws IllegalValueException { + final List patientHomeVisitDatesSlots = new ArrayList<>(); + for (JsonAdaptedDateSlot dateSlot : dateSlots) { + patientHomeVisitDatesSlots.add(dateSlot.toModelType()); + } + return patientHomeVisitDatesSlots; + } + + private Optional getModelPhysician() throws IllegalValueException { + Boolean haspName = !pName.equals(NOT_APPLICABLE); + Boolean haspPhone = !pPhone.equals(NOT_APPLICABLE); + Boolean haspEmail = !pEmail.equals(NOT_APPLICABLE); + if (!haspName && !haspPhone && !haspEmail) { + return Optional.empty(); + } + Name phyName = getModelName(pName, true, false); + Phone phyPhone = getModelPhone(pPhone, true, false); + Email phyEmail = getModelEmail(pEmail, true, false); + return Optional.of(new Physician(phyName, phyPhone, phyEmail)); + } + + private Optional getModelNextOfKin() throws IllegalValueException { + Boolean hasnName = !nName.equals(NOT_APPLICABLE); + Boolean hasnPhone = !nPhone.equals(NOT_APPLICABLE); + Boolean hasnEmail = !nEmail.equals(NOT_APPLICABLE); + if (!hasnName && !hasnPhone && !hasnEmail) { + return Optional.empty(); + } + Name nokName = getModelName(pName, false, true); + Phone nokPhone = getModelPhone(pPhone, false, true); + Email nokEmail = getModelEmail(pEmail, false, true); + return Optional.of(new NextOfKin(nokName, nokPhone, nokEmail)); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..a2e453c0103 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -34,9 +34,11 @@ public String getTagName() { } /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. + * Converts this Jackson-friendly adapted tag object into the model's + * {@code Tag} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. + * @throws IllegalValueException if there were any data constraints violated in + * the adapted tag. */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index dfab9daaa0d..7f8aea8891d 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -21,7 +21,7 @@ public class JsonAddressBookStorage implements AddressBookStorage { private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - private Path filePath; + private final Path filePath; public JsonAddressBookStorage(Path filePath) { this.filePath = filePath; diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..ae1a2d1de6e 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -19,7 +19,8 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_PERSON = "The list contains duplicate %1$s."; + public static final String PERSON_IDENTIFIER = "person(s)"; private final List persons = new ArrayList<>(); @@ -34,7 +35,8 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List readUserPrefs() throws DataConversionException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataConversionException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 6cfa0162164..ed9e9010afe 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -17,11 +17,12 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; + private final AddressBookStorage addressBookStorage; + private final UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code AddressBookStorage} + * and {@code UserPrefStorage}. */ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; @@ -45,7 +46,6 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { userPrefsStorage.saveUserPrefs(userPrefs); } - // ================ AddressBook methods ============================== @Override diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java index 29eef178dbc..79cdaa8ebcd 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -20,14 +20,18 @@ public interface UserPrefsStorage { /** * Returns UserPrefs data from storage. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected + * format. + * @throws IOException if there was any problem when reading from + * the storage. */ Optional readUserPrefs() throws DataConversionException, IOException; /** * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..b6b95c5e48e 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -27,7 +27,8 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; - // calls #setStyleToDefault() whenever there is a change to the text of the command box. + // calls #setStyleToDefault() whenever there is a change to the text of the + // command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } @@ -37,7 +38,7 @@ public CommandBox(CommandExecutor commandExecutor) { @FXML private void handleCommandEntered() { String commandText = commandTextField.getText(); - if (commandText.equals("")) { + if (commandText.isEmpty()) { return; } diff --git a/src/main/java/seedu/address/ui/CommandNameListPanel.java b/src/main/java/seedu/address/ui/CommandNameListPanel.java new file mode 100644 index 00000000000..e47997e0e98 --- /dev/null +++ b/src/main/java/seedu/address/ui/CommandNameListPanel.java @@ -0,0 +1,37 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; + +/** + * Panel containing the list of command names. + */ +public class CommandNameListPanel extends UiPart { + private static final String FXML = "CommandNameListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(CommandNameListPanel.class); + + @FXML + private ListView commandNameListView; + + /** + * Creates a {@code CommandNameListPanel} with the given {@code ObservableList}. + */ + public CommandNameListPanel(ObservableList commandNameList) { + super(FXML); + commandNameListView.setItems(commandNameList); + } + + /** + * Gets ListView + * + * @return ListView + */ + public ListView getCommandNameListView() { + return commandNameListView; + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..b806f81aa5b 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -1,13 +1,28 @@ package seedu.address.ui; +import java.awt.Desktop; +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.logging.Logger; +import javafx.animation.Animation; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; +import javafx.scene.control.ListView; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; import javafx.stage.Stage; +import javafx.util.Duration; import seedu.address.commons.core.LogsCenter; /** @@ -15,17 +30,280 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; - + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103-f13-4.github.io/tp/UserGuide.html"; + public static final String GITHUB_URL = "https://github.com/AY2223S1-CS2103-F13-4/tp"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; + private static final double DURATION_FOR_TEXT_ANIMATION = 0.1; + private static final String PATIENT_SEARCH_TEXT = "Add Patient"; + private static final String ADD_DEMO_INPUT_PATIENT = "add c/P n/John Doe g/M p/98765432 e/johnd@example.com " + + "a/311, Clementi Ave 2, #02-25 t/asthma ds/2022-11-11,2"; + private static final String ADD_DEMO_OUTPUT_PATIENT = "New Patient added: Category: P; Uid: 9; Name: John Doe;" + + " Phone: 98765432; Email: johnd@example.com; Gender: M; Address: 311, Clementi Ave 2, #02-25;" + + " Tags: [asthma] Home Visits Date and Time: [ ] [ ] 11/11/2022 12:00;"; + private static final String NURSE_SEARCH_TEXT = "Add Nurse"; + private static final String ADD_DEMO_INPUT_NURSE = "add c/N n/Cola t/pediatric e/cola@example.com g/F p/98345432" + + " a/Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03 t/heartDiseaseSpecialist ud/2022-12-12"; + private static final String ADD_DEMO_OUTPUT_NURSE = "New Nurse added: Category: N; Uid: 10; Name: Cola;" + + " Phone: 98345432; Email: cola@example.com; Gender: F;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String ADD_PATIENT_USAGE_HELP = "Add Patient \n" + + "add c/P n/Name g/Gender p/Phone e/Email a/Address " + + "[t/tag] [ds/Date&Slot] *[] is optional. "; + private static final String ADD_NURSE_USAGE_HELP = "Add Nurse\n" + + "add c/Category n/Name g/Gender p/Phone e/Email a/Address " + + "[t/tag] [ud/Unavailable Date] *[] is optional. "; + private static final String CLEAR_SEARCH_TEXT = "Clear All"; + private static final String CLEAR_DEMO_INPUT = "clear"; + private static final String CLEAR_DEMO_OUTPUT = "Healthcare Xpress record system has been cleared!"; + private static final String DELETE_SEARCH_TEXT = "Delete via ID"; + private static final String DELETE_DEMO_INPUT = "delete id/3"; + private static final String DELETE_DEMO_OUTPUT = "Deleted Nurse: Category: N; Uid: 3; Name: Cola;" + + " Phone: 98345432; Email: cola@example.com; Gender: F;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_NAME_SEARCH_TEXT = "Edit Name"; + private static final String EDIT_NAME_DEMO_INPUT = "edit id/12 n/Kola"; + private static final String EDIT_NAME_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 98345432; Email: cola@example.com; Gender: F;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_GENDER_SEARCH_TEXT = "Edit Gender"; + private static final String EDIT_GENDER_DEMO_INPUT = "edit id/12 g/M"; + private static final String EDIT_GENDER_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 98345432; Email: cola@example.com; Gender: M;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_PHONE_SEARCH_TEXT = "Edit Phone"; + private static final String EDIT_PHONE_DEMO_INPUT = "edit id/12 p/88888888"; + private static final String EDIT_PHONE_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 88888888; Email: cola@example.com; Gender: M;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_EMAIL_SEARCH_TEXT = "Edit Email"; + private static final String EDIT_EMAIL_DEMO_INPUT = "edit id/12 e/Kola@example.com"; + private static final String EDIT_EMAIL_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 88888888; Email: Kola@example.com; Gender: M;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_ADDRESS_SEARCH_TEXT = "Edit Address"; + private static final String EDIT_ADDRESS_DEMO_INPUT = "edit id/12 a/Blk 768 Woodlands Ave 6,Singapore 730768"; + private static final String EDIT_ADDRESS_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 88888888; Email: Kola@example.com; Gender: M;" + + " Address: Blk 768 Woodlands Ave 6,Singapore 730768;" + + " Tags: [heartDiseaseSpecialist][pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String EDIT_TAG_SEARCH_TEXT = "Edit Tag"; + private static final String EDIT_TAG_DEMO_INPUT = "edit id/12 t/pediatric"; + private static final String EDIT_TAG_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 88888888; Email: Kola@example.com; Gender: M;" + + " Address: Blk 768 Woodlands Ave 6,Singapore 730768;" + + " Tags: [pediatric] Unavailable Dates: 12/12/2022; No home visit assigned yet."; + private static final String ADD_UNAVAILABLE_DATE_SEARCH_TEXT = "Edit Unavailable Date (add)"; + private static final String ADD_UNAVAILABLE_DATE_DEMO_INPUT = "edit id/12 ud/2022-12-14"; + private static final String ADD_UNAVAILABLE_DATE_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 88888888; Email: Kola@example.com; Gender: M;" + + " Address: Blk 768 Woodlands Ave 6,Singapore 730768;" + + " Tags: [pediatric] Unavailable Dates: 12/12/2022, 14/12/2022; No home visit assigned yet."; + private static final String EDIT_UNAVAILABLE_DATE_SEARCH_TEXT = "Edit Unavailable Date " + + "(edit specific Unavailable Date)"; + private static final String EDIT_UNAVAILABLE_DATE_DEMO_INPUT = "edit id/12 ud/2022-12-13 udi/2"; + private static final String EDIT_UNAVAILABLE_DATE_DEMO_OUTPUT = "Edited person: Category: N Uid: 12; Name: Kola; " + + "Gender: M; Phone: 88888888; Email: Kola@example.com; Address: Blk 768 Woodlands Ave 6, Singapore 730768;" + + " Tags: [pediatric]; Unavailable Date: 12/12/2022, 13/12/2022"; + private static final String DELETE_SPECIFIC_UNAVAILABLE_DATE_SEARCH_TEXT = "Edit Unavailable Date " + + "(delete specific Unavailable Date)"; + private static final String DELETE_SPECIFIC_UD_DEMO_INPUT = "edit id/12 udi/2"; + private static final String DELETE_SPECIFIC_UD_DEMO_OUTPUT = "Edited person: Category: N Uid: 12; " + + "Name: Kola; Gender: M; Phone: 88888888; Email: Kola@example.com; " + + "Address: Blk 768 Woodlands Ave 6, Singapore 730768; Tags: [pediatric]; Unavailable Date: 12/12/2022"; + private static final String DELETE_ALL_UNAVAILABLE_DATE_SEARCH_TEXT = "Edit Unavailable Date " + + "(delete all Unavailable Date)"; + private static final String DELETE_ALL_UNAVAILABLE_DATE_DEMO_INPUT = "edit id/12 ud/"; + private static final String DELETE_ALL_UNAVAILABLE_DATE_DEMO_OUTPUT = "Edited person: Category: N Uid: 12; " + + "Name: Kola; Gender: M; Phone: 88888888; Email: Kola@example.com; " + + "Address: Blk 768 Woodlands Ave 6, Singapore 730768; Tags: [pediatric]"; + private static final String ADD_DATE_SLOT_SEARCH_TEXT = "Edit Date and Slot (add)"; + private static final String ADD_DATE_SLOT_DEMO_INPUT = "edit id/10 ds/2022-12-14,3"; + private static final String ADD_DATE_SLOT_DEMO_OUTPUT = "Edited person: Category: P Uid: 10; Name: John Doe;" + + " Gender: M; Phone: 98765432; Email: johnd@example.com; Address: 311, Clementi Ave 2, #02-25;" + + " Tags: [asthma]; Home Visits DateSlot: 11/11/2022 12:00, 14/12/2022 14:00"; + private static final String EDIT_DATE_SLOT_SEARCH_TEXT = "Edit Date and Slot (edit specific Date and Slot)"; + private static final String EDIT_DATE_SLOT_DEMO_INPUT = "edit id/10 ds/2022-12-13,3 dsi/2"; + private static final String EDIT_DATE_SLOT_DEMO_OUTPUT = "Edited person: Category: P Uid: 10; Name: John Doe;" + + " Gender: M; Phone: 98765432; Email: johnd@example.com; Address: 311, Clementi Ave 2, #02-25;" + + " Tags: [asthma]; Home Visits DateSlot: 11/11/2022 12:00, 13/12/2022 14:00"; + private static final String DELETE_SPECIFIC_DATE_SLOT_SEARCH_TEXT = "Edit Date and Slot " + + "(delete specific Date and Slot)"; + private static final String DELETE_SPECIFIC_DATE_SLOT_DEMO_INPUT = "edit id/10 dsi/2"; + private static final String DELETE_SPECIFIC_DATE_SLOT_DEMO_OUTPUT = "Edited person: Category: P Uid: 10; " + + "Name: John Doe; Gender: M; Phone: 98765432; Email: johnd@example.com; " + + "Address: 311, Clementi Ave 2, #02-25;" + + " Tags: [asthma]; Home Visits DateSlot: 11/11/2022 12:00"; + private static final String DELETE_ALL_DATE_SLOT_SEARCH_TEXT = "Edit Date and Slot " + + "(delete all Date and Slot)"; + private static final String DELETE_ALL_DATE_SLOT_DEMO_INPUT = "edit id/10 ds/"; + private static final String DELETE_ALL_DATE_SLOT_DEMO_OUTPUT = "Edited person: Category: P Uid: 10; Name: John Doe;" + + " Gender: M; Phone: 98765432; Email: johnd@example.com; Address: 311, Clementi Ave 2, #02-25;" + + " Tags: [asthma]"; + private static final String EDIT_MIX_SEARCH_TEXT = "Edit Mix"; + private static final String EDIT_MIX_DEMO_INPUT = "edit id/12 g/F p/98345432 " + + "a/Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03"; + private static final String EDIT_MIX_DEMO_OUTPUT = "Edited Nurse: Category: N; Uid: 12; Name: Kola;" + + " Phone: 98345432; Email: Kola@example.com; Gender: F;" + + " Address: Blk 431 Ang Mo Kio Ave 10, Singapore 560431 #01-03;" + + " Tags: [pediatric] Unavailable Dates: 12/12/2022, 14/12/2022; No home visit assigned yet."; + private static final String EXIT_SEARCH_TEXT = "Exit Program"; + private static final String EXIT_DEMO_INPUT = "exit"; + private static final String EXIT_DEMO_OUTPUT = "Thank you for using Healthcare Xpress!"; + private static final String FIND_SEARCH_TEXT = "Find via Name"; + private static final String FIND_DEMO_INPUT = "find Kola"; + private static final String FIND_DEMO_OUTPUT = "2 persons listed!"; + private static final String HELP_SEARCH_TEXT = "Help"; + private static final String HELP_DEMO_INPUT = "help"; + private static final String HELP_DEMO_OUTPUT = "*Brings you to this window*"; + private static final String LIST_NTH_SEARCH_TEXT = "List (Default)"; + private static final String LIST_NTH_DEMO_INPUT = "list"; + private static final String LIST_NTH_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: NIL, CATEGORY: NIL, GENDER: NIL, TAG: NIL"; + private static final String LIST_ADDRESS_SEARCH_TEXT = "List all with X Address"; + private static final String LIST_ADDRESS_DEMO_INPUT = "list a/431"; + private static final String LIST_ADDRESS_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: 431, CATEGORY: NIL, GENDER: NIL, TAG: NIL"; + private static final String LIST_CATEGORY_SEARCH_TEXT = "List all with X Category"; + private static final String LIST_CATEGORY_DEMO_INPUT = "list c/N"; + private static final String LIST_CATEGORY_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: NIL, CATEGORY: N, GENDER: NIL, TAG: NIL"; + private static final String LIST_GENDER_SEARCH_TEXT = "List all with X Gender"; + private static final String LIST_GENDER_DEMO_INPUT = "list g/F"; + private static final String LIST_GENDER_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: NIL, CATEGORY: NIL, GENDER: F, TAG: NIL"; + private static final String LIST_TAG_SEARCH_TEXT = "List all with X Tag"; + private static final String LIST_TAG_DEMO_INPUT = "list t/Pediatric"; + private static final String LIST_TAG_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: NIL, CATEGORY: NIL, GENDER: NIL, TAG: Pediatric"; + private static final String LIST_MIX_SEARCH_TEXT = "List (Mix)"; + private static final String LIST_MIX_DEMO_INPUT = "list a/431 g/F"; + private static final String LIST_MIX_DEMO_OUTPUT = "Listed all persons with specifications: " + + "ADDRESS: 431, CATEGORY: NIL, GENDER: F, TAG: NIL"; + private static final String CLEAR_USAGE_HELP = ""; + private static final String DELETE_USAGE_HELP = "Delete Person from list: \n" + + "delete id/ID "; + private static final String EDIT_USAGE_HELP = "Edit Person from list: \n" + + "edit [n/Name] [g/gender] [p/Phone] [e/Email] [a/Address] [t/Tags] [ds/Date&Slot] [dsi/Date&Slot Index] " + + "[ud/Unavailable Date] [udi/Unavailable Date Index]" + + "*[] is optional.\n" + "*Date and Slot and its index is only applicable to Patient.\n" + + "*Unavailable Date and its index is only applicable to Nurse. "; + private static final String EXIT_USAGE_HELP = ""; + private static final String FIND_USAGE_HELP = "Find a person from the list that contains keyword\n" + + "find KEYWORD"; + private static final String HELP_USAGE_HELP = ""; + private static final String LIST_USAGE_HELP = "List all that meets the criteria:\n" + + "list [c/Category] [g/Gender] [a/Address] [t/Tag]\n" + + "*[] is optional"; + private static final String UPCOMING_FEATURE = "This is a upcoming feature " + + "and have not been implemented"; + private static final String LIST_ASSIGN_SEARCH_TEXT = "List (Assign)"; + private static final String LIST_ASSIGN_DEMO_INPUT = "list c/P as/true"; + private static final String LIST_ASSIGN_DEMO_OUTPUT = UPCOMING_FEATURE; + private static final String LIST_VISIT_STATUS_SEARCH_TEXT = "List (Visit Status)"; + private static final String LIST_VISIT_STATUS_DEMO_INPUT = "list c/P v/true"; + private static final String LIST_VISIT_STATUS_DEMO_OUTPUT = UPCOMING_FEATURE; + private static final String ASSIGN_SPECIFIC_SEARCH_TEXT = "Assign Specific HomeVisit Date&Slot"; + private static final String ASSIGN_SPECIFIC_DEMO_INPUT = "assign id/2 id/3 dsi/2"; + private static final String ASSIGN_SPECIFIC_DEMO_OUTPUT = "3's dateslot/dateslots assigned to 2." + + " (Assign the second date slot of patient with uid of 3 to nurse with uid of 2) "; + private static final String ASSIGN_ALL_SEARCH_TEXT = "Assign All HomeVisit Date&Slot from A Patient"; + private static final String ASSIGN_ALL_DEMO_INPUT = "assign id/2 id/3"; + private static final String ASSIGN_ALL_DEMO_OUTPUT = "3's dateslot/dateslots assigned to 2." + + " (Assign all the date slot of patient with uid of 3 to nurse with uid of 2) "; + private static final String ASSIGN_USAGE_HELP = "Assigns dateslots of the patient with the specified " + + "‘PATIENT_ID’ to the nurse with the specified ‘NURSE_ID’.\n" + + "assign id/NURSE_ID id/PATIENT_ID [dsi/DATE_AND_SLOT_INDEX]\n" + + "*[] is optional"; + private static final String DEASSIGN_ALL_PATIENT_SEARCH_TEXT = "Deassign All HomeVisit Date&Slot from a Patient"; + private static final String DEASSIGN_ALL_PATIENT_DEMO_INPUT = "deassign id/3"; + private static final String DEASSIGN_ALL_PATIENT_DEMO_OUTPUT = "3 's dateslot/homevisit has been deassigned. " + + "(All the date and slot from patient with uid of 3 has been deassigned)"; + private static final String DEASSIGN_SPECIFIC_PATIENT_SEARCH_TEXT = "Deassign Specific HomeVisit Date&Slot " + + "from a Patient"; + private static final String DEASSIGN_SPECIFIC_PATIENT_DEMO_INPUT = "deassign id/3 dsi/1"; + private static final String DEASSIGN_SPECIFIC_PATIENT_DEMO_OUTPUT = "3 's dateslot/homevisit has been deassigned. " + + "(The first date and slot from patient with uid of 3 has been deassigned)"; + private static final String DEASSIGN_ALL_NURSE_SEARCH_TEXT = "Deassign All HomeVisits from a Nurse"; + private static final String DEASSIGN_ALL_NURSE_DEMO_INPUT = "deassign id/2"; + private static final String DEASSIGN_ALL_NURSE_DEMO_OUTPUT = "2 's dateslot/homevisit has been deassigned. " + + "(All the homevisits from nurse with uid of 2 has been deassigned)"; + private static final String DEASSIGN_SPECIFIC_NURSE_SEARCH_TEXT = "Deassign Specific HomeVisit " + + "from a Nurse"; + private static final String DEASSIGN_SPECIFIC_NURSE_DEMO_INPUT = "deassign id/2 dsi/1"; + private static final String DEASSIGN_SPECIFIC_NURSE_DEMO_OUTPUT = "2 's dateslot/homevisit has been deassigned. " + + "(The first homevisit from nurse with uid of 2 has been deassigned)"; + private static final String DEASSIGN_USAGE_HELP = "Deassign dateslot / homevisit.\n" + + "deassign id/ID [dsi/DATE_AND_SLOT_INDEX]\n" + + "*[] is optional\n"; + private static final String UNMARK_USAGE_HELP = "Unmarks the patient with the specified ‘ID’ as having been failed" + + " visited.\n" + + "unmark id/PATIENT_ID dsi/DATE_AND_SLOT_INDEX"; + private static final String UNMARK_SEARCH_TEXT = "Unmarks a patient's date and slot"; + private static final String UNMARK_DEMO_INPUT = "unmark id/7 dsi/1"; + private static final String UNMARK_DEMO_OUTPUT = "Unmarked Patient as fail to visit: Category: P; Uid: 7;" + + " Name: Irfan Ibrahim; Phone: 92492021; Email: irfan@example.com; Gender: M; " + + "Address: Blk 47 Tampines Street 20, #17-35;; Tags: [classmates] " + + "Home Visits Date and Time: [ ] [FV] 24/09/2022 10:00,[ ] [V] 10/10/2022 10:00,[ ] [ ] 13/01/2023 14:00;"; + private static final String UNDO_UNMARK_SEARCH_TEXT = "Undo Unmark"; + private static final String UNDO_UNMARK_DEMO_INPUT = "undounmark id/7 dsi/1"; + private static final String UNDO_UNMARK_DEMO_OUTPUT = "Undo Unmarked Patient as success visit: Category: P; Uid: 7;" + + " Name: Irfan Ibrahim; Phone: 92492021; Email: irfan@example.com; Gender: M; " + + "Address: Blk 47 Tampines Street 20, #17-35;; Tags: [classmates] " + + "Home Visits Date and Time: [ ] [V] 24/09/2022 10:00,[ ] [V] 10/10/2022 10:00,[ ] [ ] 13/01/2023 14:00;"; + private static final String UNDO_UNMARK_USAGE_HELP = "Undo Unmarks a specific patient’s specific dateslot in the " + + "records system as having been visited.\n" + + "undounmark id/ID dsi/DATE_SLOT_INDEX"; + private static final String CHECK_SIMILAR_SEARCH_TEXT = "Check Similar"; + private static final String CHECK_SIMILAR_DEMO_INPUT = "checkSimilar"; + private static final String CHECK_SIMILAR_DEMO_OUTPUT = "*List of similar people*"; + private static final String CHECK_SIMILAR_USAGE_HELP = "checkSimilar checks Name, Category, Phone number, Email," + + " Gender, Tags, Address and returns both person if they match 5 of these."; + private static final String UPDATE_CONTACT_SEARCH_TEXT = "Update Contact"; + private static final String UPDATE_CONTACT_DEMO_INPUT = "updatecontact id/3 c/K n/John Doe p/81234567 " + + "e/johndoe@example.com"; + private static final String UPDATE_CONTACT_DEMO_OUTPUT = "Added contact details to patient with UID: 3, " + + "Physician Name: John Doe, Phone: 81234567, Email: johndoe@example.com, Category: K"; + private static final String UPDATE_CONTACT_USAGE_HELP = "Updates a patient’s contact information for next-of-kin " + + "or attending physician.\n" + + "Format: updatecontact id/PATIENT_ID c/CATEGORY n/CONTACT_NAME p/CONTACT_PHONE e/CONTACT_EMAIL"; + private final List commandList = Arrays.asList(PATIENT_SEARCH_TEXT, NURSE_SEARCH_TEXT, CLEAR_SEARCH_TEXT, + DELETE_SEARCH_TEXT, EDIT_NAME_SEARCH_TEXT, EDIT_GENDER_SEARCH_TEXT, EDIT_PHONE_SEARCH_TEXT, + EDIT_EMAIL_SEARCH_TEXT, EDIT_TAG_SEARCH_TEXT, ADD_UNAVAILABLE_DATE_SEARCH_TEXT, + EDIT_UNAVAILABLE_DATE_SEARCH_TEXT, DELETE_SPECIFIC_UNAVAILABLE_DATE_SEARCH_TEXT, + DELETE_ALL_UNAVAILABLE_DATE_SEARCH_TEXT, ADD_DATE_SLOT_SEARCH_TEXT, EDIT_DATE_SLOT_SEARCH_TEXT, + DELETE_SPECIFIC_DATE_SLOT_SEARCH_TEXT, DELETE_ALL_DATE_SLOT_SEARCH_TEXT, EDIT_MIX_SEARCH_TEXT, + EXIT_SEARCH_TEXT, FIND_SEARCH_TEXT, HELP_SEARCH_TEXT, LIST_NTH_SEARCH_TEXT, LIST_ADDRESS_SEARCH_TEXT, + LIST_CATEGORY_SEARCH_TEXT, LIST_GENDER_SEARCH_TEXT, LIST_TAG_SEARCH_TEXT, LIST_MIX_SEARCH_TEXT, + LIST_VISIT_STATUS_SEARCH_TEXT, LIST_ASSIGN_SEARCH_TEXT, ASSIGN_SPECIFIC_SEARCH_TEXT, ASSIGN_ALL_SEARCH_TEXT, + DEASSIGN_ALL_PATIENT_SEARCH_TEXT, DEASSIGN_SPECIFIC_PATIENT_SEARCH_TEXT, DEASSIGN_ALL_NURSE_SEARCH_TEXT, + DEASSIGN_SPECIFIC_NURSE_SEARCH_TEXT, UNMARK_SEARCH_TEXT, UNDO_UNMARK_SEARCH_TEXT, + CHECK_SIMILAR_SEARCH_TEXT, UPDATE_CONTACT_SEARCH_TEXT); + private final HashMap dictionary = new HashMap(); + private final HashMap dictionaryForUsageHelp = new HashMap(); + + private CommandNameListPanel commandNameListPanel; + private Timeline currentTimeLine; + + @FXML + private VBox resultDisplayPlaceholder; @FXML - private Button copyButton; + private TextArea tipTextArea; + @FXML + private TextField searchTextField; + + @FXML + private TextField helpCommandTextField; @FXML - private Label helpMessage; + private TextField helpOutputTextField; /** * Creates a new HelpWindow. @@ -34,7 +312,7 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + init(); } /** @@ -44,23 +322,147 @@ public HelpWindow() { this(new Stage()); } + /** + * Initialize HelpWindow, dictionary and add listener to ListView. + */ + public void init() { + initDictionary(); + initDictionaryForUsageHelp(); + String emptyStr = ""; + String[] result = searchResult(emptyStr); + ObservableList obvListResult = convertStrArrToObservableList(result); + fillResultDisplay(obvListResult); + addListenerToListView(); + } + + /** + * Initialize dictionary. + */ + public void initDictionary() { + dictionary.put(PATIENT_SEARCH_TEXT, new String[] { ADD_DEMO_INPUT_PATIENT, ADD_DEMO_OUTPUT_PATIENT }); + dictionary.put(NURSE_SEARCH_TEXT, new String[] { ADD_DEMO_INPUT_NURSE, ADD_DEMO_OUTPUT_NURSE }); + dictionary.put(CLEAR_SEARCH_TEXT, new String[] { CLEAR_DEMO_INPUT, CLEAR_DEMO_OUTPUT }); + dictionary.put(DELETE_SEARCH_TEXT, new String[] { DELETE_DEMO_INPUT, DELETE_DEMO_OUTPUT }); + dictionary.put(EDIT_NAME_SEARCH_TEXT, new String[] { EDIT_NAME_DEMO_INPUT, EDIT_NAME_DEMO_OUTPUT }); + dictionary.put(EDIT_GENDER_SEARCH_TEXT, new String[] { EDIT_GENDER_DEMO_INPUT, EDIT_GENDER_DEMO_OUTPUT }); + dictionary.put(EDIT_PHONE_SEARCH_TEXT, new String[] { EDIT_PHONE_DEMO_INPUT, EDIT_PHONE_DEMO_OUTPUT }); + dictionary.put(EDIT_EMAIL_SEARCH_TEXT, new String[] { EDIT_EMAIL_DEMO_INPUT, EDIT_EMAIL_DEMO_OUTPUT }); + dictionary.put(EDIT_ADDRESS_SEARCH_TEXT, new String[] { EDIT_ADDRESS_DEMO_INPUT, EDIT_ADDRESS_DEMO_OUTPUT }); + dictionary.put(EDIT_TAG_SEARCH_TEXT, new String[] { EDIT_TAG_DEMO_INPUT, EDIT_TAG_DEMO_OUTPUT }); + dictionary.put(ADD_UNAVAILABLE_DATE_SEARCH_TEXT, + new String[] { ADD_UNAVAILABLE_DATE_DEMO_INPUT, ADD_UNAVAILABLE_DATE_DEMO_OUTPUT }); + dictionary.put(EDIT_UNAVAILABLE_DATE_SEARCH_TEXT, + new String[] { EDIT_UNAVAILABLE_DATE_DEMO_INPUT, EDIT_UNAVAILABLE_DATE_DEMO_OUTPUT }); + dictionary.put(DELETE_SPECIFIC_UNAVAILABLE_DATE_SEARCH_TEXT, + new String[] { DELETE_SPECIFIC_UD_DEMO_INPUT, DELETE_SPECIFIC_UD_DEMO_OUTPUT }); + dictionary.put(DELETE_ALL_UNAVAILABLE_DATE_SEARCH_TEXT, + new String[] { DELETE_ALL_UNAVAILABLE_DATE_DEMO_INPUT, DELETE_ALL_UNAVAILABLE_DATE_DEMO_OUTPUT }); + dictionary.put(ADD_DATE_SLOT_SEARCH_TEXT, + new String[] { ADD_DATE_SLOT_DEMO_INPUT, ADD_DATE_SLOT_DEMO_OUTPUT }); + dictionary.put(EDIT_DATE_SLOT_SEARCH_TEXT, + new String[] { EDIT_DATE_SLOT_DEMO_INPUT, EDIT_DATE_SLOT_DEMO_OUTPUT }); + dictionary.put(DELETE_SPECIFIC_DATE_SLOT_SEARCH_TEXT, + new String[] { DELETE_SPECIFIC_DATE_SLOT_DEMO_INPUT, DELETE_SPECIFIC_DATE_SLOT_DEMO_OUTPUT }); + dictionary.put(DELETE_ALL_DATE_SLOT_SEARCH_TEXT, + new String[] { DELETE_ALL_DATE_SLOT_DEMO_INPUT, DELETE_ALL_DATE_SLOT_DEMO_OUTPUT }); + dictionary.put(EDIT_MIX_SEARCH_TEXT, new String[] { EDIT_MIX_DEMO_INPUT, EDIT_MIX_DEMO_OUTPUT }); + dictionary.put(EXIT_SEARCH_TEXT, new String[] { EXIT_DEMO_INPUT, EXIT_DEMO_OUTPUT }); + dictionary.put(FIND_SEARCH_TEXT, new String[] { FIND_DEMO_INPUT, FIND_DEMO_OUTPUT }); + dictionary.put(HELP_SEARCH_TEXT, new String[] { HELP_DEMO_INPUT, HELP_DEMO_OUTPUT }); + dictionary.put(LIST_NTH_SEARCH_TEXT, new String[] { LIST_NTH_DEMO_INPUT, LIST_NTH_DEMO_OUTPUT }); + dictionary.put(LIST_ADDRESS_SEARCH_TEXT, new String[] { LIST_ADDRESS_DEMO_INPUT, LIST_ADDRESS_DEMO_OUTPUT }); + dictionary.put(LIST_CATEGORY_SEARCH_TEXT, new String[] { LIST_CATEGORY_DEMO_INPUT, LIST_CATEGORY_DEMO_OUTPUT }); + dictionary.put(LIST_GENDER_SEARCH_TEXT, new String[] { LIST_GENDER_DEMO_INPUT, LIST_GENDER_DEMO_OUTPUT }); + dictionary.put(LIST_TAG_SEARCH_TEXT, new String[] { LIST_TAG_DEMO_INPUT, LIST_TAG_DEMO_OUTPUT }); + dictionary.put(LIST_ASSIGN_SEARCH_TEXT, new String[] { LIST_ASSIGN_DEMO_INPUT, LIST_ASSIGN_DEMO_OUTPUT }); + dictionary.put(LIST_VISIT_STATUS_SEARCH_TEXT, + new String[] { LIST_VISIT_STATUS_DEMO_INPUT, LIST_VISIT_STATUS_DEMO_OUTPUT }); + dictionary.put(LIST_MIX_SEARCH_TEXT, new String[] { LIST_MIX_DEMO_INPUT, LIST_MIX_DEMO_OUTPUT }); + dictionary.put(ASSIGN_SPECIFIC_SEARCH_TEXT, + new String[] { ASSIGN_SPECIFIC_DEMO_INPUT, ASSIGN_SPECIFIC_DEMO_OUTPUT }); + dictionary.put(ASSIGN_ALL_SEARCH_TEXT, + new String[] { ASSIGN_ALL_DEMO_INPUT, ASSIGN_ALL_DEMO_OUTPUT }); + dictionary.put(DEASSIGN_ALL_PATIENT_SEARCH_TEXT, + new String[] { DEASSIGN_ALL_PATIENT_DEMO_INPUT, DEASSIGN_ALL_PATIENT_DEMO_OUTPUT }); + dictionary.put(DEASSIGN_SPECIFIC_PATIENT_SEARCH_TEXT, + new String[] { DEASSIGN_SPECIFIC_PATIENT_DEMO_INPUT, DEASSIGN_SPECIFIC_PATIENT_DEMO_OUTPUT }); + dictionary.put(DEASSIGN_ALL_NURSE_SEARCH_TEXT, + new String[] { DEASSIGN_ALL_NURSE_DEMO_INPUT, DEASSIGN_ALL_NURSE_DEMO_OUTPUT }); + dictionary.put(DEASSIGN_SPECIFIC_NURSE_SEARCH_TEXT, + new String[] { DEASSIGN_SPECIFIC_NURSE_DEMO_INPUT, DEASSIGN_SPECIFIC_NURSE_DEMO_OUTPUT }); + dictionary.put(UNMARK_SEARCH_TEXT, new String[] { UNMARK_DEMO_INPUT, UNMARK_DEMO_OUTPUT }); + dictionary.put(UNDO_UNMARK_SEARCH_TEXT, new String[] { UNDO_UNMARK_DEMO_INPUT, UNDO_UNMARK_DEMO_OUTPUT }); + dictionary.put(CHECK_SIMILAR_SEARCH_TEXT, new String[] { CHECK_SIMILAR_DEMO_INPUT, CHECK_SIMILAR_DEMO_OUTPUT }); + dictionary.put(UPDATE_CONTACT_SEARCH_TEXT, + new String[] { UPDATE_CONTACT_DEMO_INPUT, UPDATE_CONTACT_DEMO_OUTPUT }); + } + + /** + * Initialize dictionary for usage help. + */ + public void initDictionaryForUsageHelp() { + dictionaryForUsageHelp.put(PATIENT_SEARCH_TEXT, ADD_PATIENT_USAGE_HELP); + dictionaryForUsageHelp.put(NURSE_SEARCH_TEXT, ADD_NURSE_USAGE_HELP); + dictionaryForUsageHelp.put(CLEAR_SEARCH_TEXT, CLEAR_USAGE_HELP); + dictionaryForUsageHelp.put(DELETE_SEARCH_TEXT, DELETE_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_NAME_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_GENDER_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_PHONE_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_EMAIL_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_ADDRESS_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_TAG_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(ADD_UNAVAILABLE_DATE_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_UNAVAILABLE_DATE_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(DELETE_SPECIFIC_UNAVAILABLE_DATE_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(DELETE_ALL_UNAVAILABLE_DATE_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(ADD_DATE_SLOT_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_DATE_SLOT_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(DELETE_SPECIFIC_DATE_SLOT_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(DELETE_ALL_DATE_SLOT_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EDIT_MIX_SEARCH_TEXT, EDIT_USAGE_HELP); + dictionaryForUsageHelp.put(EXIT_SEARCH_TEXT, EXIT_USAGE_HELP); + dictionaryForUsageHelp.put(FIND_SEARCH_TEXT, FIND_USAGE_HELP); + dictionaryForUsageHelp.put(HELP_SEARCH_TEXT, HELP_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_NTH_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_ADDRESS_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_CATEGORY_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_GENDER_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_TAG_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_MIX_SEARCH_TEXT, LIST_USAGE_HELP); + dictionaryForUsageHelp.put(LIST_VISIT_STATUS_SEARCH_TEXT, UPCOMING_FEATURE); + dictionaryForUsageHelp.put(LIST_ASSIGN_SEARCH_TEXT, UPCOMING_FEATURE); + dictionaryForUsageHelp.put(ASSIGN_SPECIFIC_SEARCH_TEXT, ASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(ASSIGN_ALL_SEARCH_TEXT, ASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(DEASSIGN_ALL_PATIENT_SEARCH_TEXT, DEASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(DEASSIGN_SPECIFIC_PATIENT_SEARCH_TEXT, DEASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(DEASSIGN_ALL_NURSE_SEARCH_TEXT, DEASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(DEASSIGN_SPECIFIC_NURSE_SEARCH_TEXT, DEASSIGN_USAGE_HELP); + dictionaryForUsageHelp.put(UNMARK_SEARCH_TEXT, UNMARK_USAGE_HELP); + dictionaryForUsageHelp.put(UNDO_UNMARK_SEARCH_TEXT, UNDO_UNMARK_USAGE_HELP); + dictionaryForUsageHelp.put(CHECK_SIMILAR_SEARCH_TEXT, CHECK_SIMILAR_USAGE_HELP); + dictionaryForUsageHelp.put(UPDATE_CONTACT_SEARCH_TEXT, UPDATE_CONTACT_USAGE_HELP); + } + /** * Shows the help window. + * * @throws IllegalStateException - *

    - *
  • - * if this method is called on a thread other than the JavaFX Application Thread. - *
  • - *
  • - * if this method is called during animation or layout processing. - *
  • - *
  • - * if this method is called on the primary stage. - *
  • - *
  • - * if {@code dialogStage} is already showing. - *
  • - *
+ *
    + *
  • + * if this method is called on a thread other than + * the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or + * layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
*/ public void show() { logger.fine("Showing help page about the application."); @@ -90,13 +492,131 @@ public void focus() { } /** - * Copies the URL to the user guide to the clipboard. + * Searches the command list with keyword str. + * + * @param str Keyword. + * @return An array of strings that fits the criteria. + */ + public String[] searchResult(String str) { + String strLowerCase = str.toLowerCase(); + return commandList.stream().filter(x -> x.toLowerCase().contains(strLowerCase)).toArray(String[]::new); + } + + /** + * Populates the display in HelpWindow. + * + * @param input An ObservableList of Strings. + */ + public void fillResultDisplay(ObservableList input) { + commandNameListPanel = new CommandNameListPanel(input); + resultDisplayPlaceholder.getChildren().add(commandNameListPanel.getRoot()); + } + + /** + * Converts String array to ObservableList. + * + * @param strArr String array to be converted. + * @return An Observable List. + */ + public ObservableList convertStrArrToObservableList(String[] strArr) { + return FXCollections.observableArrayList(strArr); + } + + /** + * Adds listener to ListView to detect changes. If changes is detected, plays + * the corresponding animation. + */ + public void addListenerToListView() { + ListView listView = commandNameListPanel.getCommandNameListView(); + listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + stopAnimationIfAny(); + String[] inputOutput = dictionary.get(newValue); + helpOutputTextField.clear(); + animateTextInTextField(inputOutput, helpCommandTextField, false); + String usageHelp = dictionaryForUsageHelp.get(newValue); + tipTextArea.setText(usageHelp); + } + }); + } + + /** + * Stops any ongoing animation. + */ + public void stopAnimationIfAny() { + if (currentTimeLine != null) { + currentTimeLine.stop(); + } + } + + /** + * Animates text in textfield. + * + * @param inputOutput A String array containing the input and output result. + * @param txtField The javafx textfield to be animated on. + * @param isOutput Check if it is on input's or on output's textfield. + */ + public void animateTextInTextField(String[] inputOutput, TextField txtField, boolean isOutput) { + String str = inputOutput[isOutput ? 1 : 0]; + final IntegerProperty counter = new SimpleIntegerProperty(0); + Timeline timeline = new Timeline(); + KeyFrame keyFrame = new KeyFrame( + Duration.seconds(DURATION_FOR_TEXT_ANIMATION), + event -> { + if (counter.get() > str.length()) { + timeline.stop(); + if (!isOutput) { + animateTextInTextField(inputOutput, helpOutputTextField, true); + } + } else { + txtField.setText(str.substring(0, counter.get())); + txtField.positionCaret(counter.get()); + counter.set(counter.get() + 1); + } + }); + timeline.getKeyFrames().add(keyFrame); + timeline.setCycleCount(Animation.INDEFINITE); + timeline.play(); + currentTimeLine = timeline; + } + + /** + * Sends user to GitHub page. + */ + @FXML + public void visitGitHubButtonAction() { + try { + Desktop.getDesktop().browse(new URI(GITHUB_URL)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Sends user to user guide page. */ @FXML - private void copyUrl() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); - clipboard.setContent(url); + public void visitHelpButtonAction() { + try { + Desktop.getDesktop().browse(new URI(USERGUIDE_URL)); + } catch (Exception e) { + e.printStackTrace(); + } } + + /** + * Handles and show accordingly to User the filtered List View according to + * User's search keyword. + */ + @FXML + public void handleSearchKeywordEntered() { + String searchText = searchTextField.getText().trim(); + resultDisplayPlaceholder.getChildren().clear(); + String[] result = searchResult(searchText); + ObservableList obvListResult = convertStrArrToObservableList(result); + fillResultDisplay(obvListResult); + addListenerToListView(); + } + } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..9eb05df843a 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -27,13 +27,13 @@ public class MainWindow extends UiPart { private final Logger logger = LogsCenter.getLogger(getClass()); - private Stage primaryStage; - private Logic logic; + private final Stage primaryStage; + private final Logic logic; // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; private ResultDisplay resultDisplay; - private HelpWindow helpWindow; + private final HelpWindow helpWindow; @FXML private StackPane commandBoxPlaceholder; @@ -78,6 +78,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..ab5e199c5e8 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,5 +1,7 @@ package seedu.address.ui; +import static seedu.address.model.person.Gender.MALE_SYMBOL; + import java.util.Comparator; import javafx.fxml.FXML; @@ -7,6 +9,8 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import seedu.address.model.person.Nurse; +import seedu.address.model.person.Patient; import seedu.address.model.person.Person; /** @@ -15,13 +19,21 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; + private static final String NOT_APPLICABLE = "NA"; + private static final String NURSE_LABEL_TEXT = "(Nurse)"; + private static final String PATIENT_LABEL_TEXT = "(Patient)"; + private static final String NAN_LABEL_TEXT = "(Unassigned)"; + private static final String MALE_GENDER_LABEL_TEXT = "Male"; + private static final String FEMALE_GENDER_LABEL_TEXT = "Female"; /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * 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 + * @see The + * issue on AddressBook level 4 */ public final Person person; @@ -31,8 +43,16 @@ public class PersonCard extends UiPart { @FXML private Label name; @FXML + private FlowPane category; + @FXML + private Label uid; + @FXML + private Label dateSlots; + @FXML private Label id; @FXML + private Label gender; + @FXML private Label phone; @FXML private Label address; @@ -40,15 +60,53 @@ public class PersonCard extends UiPart { private Label email; @FXML private FlowPane tags; + @FXML + private Label homeVisits; + @FXML + private Label unavailableDates; + @FXML + private Label physInfo; + @FXML + private Label nokInfo; /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code PersonCode} with the given {@code Person} and index to + * display. */ public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); + + if (person.isNurse()) { + dateSlots.setText("HomeVisits DateSlot: " + NOT_APPLICABLE); + homeVisits.setText(((Nurse) person).getHomesVisitsInString()); + unavailableDates.setText(((Nurse) person).getUnavailableDatesInString()); + category.getChildren().add(new Label(NURSE_LABEL_TEXT)); + physInfo.setText("Attending Physician: " + NOT_APPLICABLE); + nokInfo.setText("NOK: " + NOT_APPLICABLE); + } else if (person.isPatient()) { + dateSlots.setText(((Patient) person).getDatesSlotsInString()); + homeVisits.setText("HomeVisits: " + NOT_APPLICABLE); + unavailableDates.setText("Unavailable Dates: " + NOT_APPLICABLE); + category.getChildren().add(new Label(PATIENT_LABEL_TEXT)); + physInfo.setText(((Patient) person).getPhysicianDetails()); + nokInfo.setText(((Patient) person).getNextOfKinDetails()); + } else { + dateSlots.setText(NOT_APPLICABLE); + homeVisits.setText(NOT_APPLICABLE); + unavailableDates.setText(NOT_APPLICABLE); + category.getChildren().add(new Label(NAN_LABEL_TEXT)); + physInfo.setText(NOT_APPLICABLE); + nokInfo.setText(NOT_APPLICABLE); + } name.setText(person.getName().fullName); + if (person.getGender().gender.equals(MALE_SYMBOL)) { + gender.setText(MALE_GENDER_LABEL_TEXT); + } else { + gender.setText(FEMALE_GENDER_LABEL_TEXT); + } + uid.setText("UID: [" + person.getUid().toString() + "]"); phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..e9e706649d1 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -30,9 +30,10 @@ public PersonListPanel(ObservableList personList) { } /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + * Custom {@code ListCell} that displays the graphics of a {@code Person} using + * a {@code PersonCard}. */ - class PersonListViewCell extends ListCell { + static class PersonListViewCell extends ListCell { @Override protected void updateItem(Person person, boolean empty) { super.updateItem(person, empty); diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java index 17aa0b494fe..e28dae7e990 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/address/ui/Ui.java @@ -7,7 +7,9 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** + * Starts the UI (and the App). + */ void start(Stage primaryStage); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..1a4fff64f2b 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,9 +20,9 @@ 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/healthcare_xpress_logo.png"; - private Logic logic; + private final 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. @@ -84,5 +84,4 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { Platform.exit(); System.exit(1); } - } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..bf2c130fac6 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -9,12 +9,15 @@ import seedu.address.MainApp; /** - * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. + * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status + * bars, etc. * It contains a scene graph with a root node of type {@code T}. */ public abstract class UiPart { - /** Resource folder where FXML files are stored. */ + /** + * Resource folder where FXML files are stored. + */ public static final String FXML_FILE_FOLDER = "/view/"; private final FXMLLoader fxmlLoader = new FXMLLoader(); @@ -28,7 +31,9 @@ public UiPart(URL fxmlFileUrl) { } /** - * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * Constructs a UiPart using the specified FXML file within + * {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -44,13 +49,26 @@ public UiPart(URL fxmlFileUrl, T root) { } /** - * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * Constructs a UiPart with the specified FXML file within + * {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { this(getFxmlFileUrl(fxmlFileName), root); } + /** + * Returns the FXML file URL for the specified FXML file name within + * {@link #FXML_FILE_FOLDER}. + */ + private static URL getFxmlFileUrl(String fxmlFileName) { + requireNonNull(fxmlFileName); + String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; + URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + return requireNonNull(fxmlFileUrl); + } + /** * Returns the root object of the scene graph of this UiPart. */ @@ -60,8 +78,9 @@ public T getRoot() { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -75,14 +94,4 @@ private void loadFxmlFile(URL location, T root) { } } - /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. - */ - private static URL getFxmlFileUrl(String fxmlFileName) { - requireNonNull(fxmlFileName); - String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); - return requireNonNull(fxmlFileUrl); - } - } diff --git a/src/main/resources/images/healthcare_xpress_logo.png b/src/main/resources/images/healthcare_xpress_logo.png new file mode 100644 index 00000000000..a73282e846a Binary files /dev/null and b/src/main/resources/images/healthcare_xpress_logo.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..3b2f1ee08f9 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,7 @@ - - + + diff --git a/src/main/resources/view/CommandNameListPanel.fxml b/src/main/resources/view/CommandNameListPanel.fxml new file mode 100644 index 00000000000..214115db8a0 --- /dev/null +++ b/src/main/resources/view/CommandNameListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..0906e2c60c7 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -1,19 +1,322 @@ -#copyButton, #helpMessage { +.background { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #3c3e3f; +} + +.list-cell:filled:odd { + -fx-background-color: #515658; +} + +.list-cell:filled:selected { + -fx-background-color: #424d5f; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: white; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.stack-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { -fx-text-fill: white; } -#copyButton { - -fx-background-color: dimgray; +.grid-pane { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 30%); + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: derive(#1d1d1d, 30%); } -#copyButton:hover { - -fx-background-color: gray; +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); } -#copyButton:armed { - -fx-background-color: darkgray; +.context-menu .label { + -fx-text-fill: white; } -#helpMessageContainer { +.menu-bar { -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 20%) derive(#1d1d1d, 20%) grey derive(#1d1d1d, 20%); +} + +.menu-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +#searchTextField { + -fx-background-color: transparent #383838 transparent #383838; + -fx-background-insets: 0; + -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +#resultDisplay .content { + -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +.list-cell{ + -fx-font-size:15.0; + -fx-text-fill: white; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell:filled:even { + -fx-background-color: #3c3e3f; +} + +.list-cell:filled:hover { + -fx-background-color: #566a8c; + -fx-text-fill: white; +} + +.list-cell:filled:selected { + -fx-background-color: #0093ff; +} + +#helpCommandTextField { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +#helpOutputTextField { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +#tipTextArea { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Light"; } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..16681c0b1b1 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,44 +1,180 @@ - - + + + + + + + + + + - - - - - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +