diff --git a/.github/workflows/gradle_notest.yml b/.github/workflows/gradle_notest.yml new file mode 100644 index 00000000000..935514ca086 --- /dev/null +++ b/.github/workflows/gradle_notest.yml @@ -0,0 +1,40 @@ +name: Java Alt CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up repository + uses: actions/checkout@v2 # Note: Changed to @v2 for better practices + + - name: Merge to master + run: git checkout --progress --force ${{ github.sha }} + + - name: Run repository-wide tests + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }}/.github + run: ./run-checks.sh + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK 11 + uses: actions/setup-java@v2 # Note: Updated to @v2 for better practices + with: + distribution: 'temurin' # Use Temurin distribution + java-version: '11' + + - name: Build with Gradle without running tests + run: ./gradlew build -x test # Changed to build without tests + + - name: Upload coverage reports to Codecov + if: runner.os == 'Linux' + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index 13f5c77403f..840d4cdbc1e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2324S2-CS2103T-T08-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S2-CS2103T-T08-1/tp/actions) -![Ui](docs/images/Ui.png) +![Ui](docs/images/GUI.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* **ImmuniMate** is a JavaFX application with a GUI, aimed at helping General Practitioners and Healthcare Professionals better keep track of patient Information.
+ Main Features: + * Easily create patient profile and update patient information to ImmuniMate + * Keep track of infected patients and clusters of information + * Search for patients and patient history with a breeze +* The name `ImmuniMate` is chosen to reflect its purpose of enhancing and safeguarding community immunity. +* For the detailed documentation and information on ImmuniMate, please visit our **[website](https://ay2324s2-cs2103t-t08-1.github.io/tp/)**. +* This project is currently being developed by a student team in NUS. Our Source code for the project can be found **[here on github.](https://github.com/AY2324S2-CS2103T-T08-1/tp)** diff --git a/build.gradle b/build.gradle index a2951cc709e..face0e9a30a 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,10 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'immuniMate.jar' } +run { + enableAssertions = true +} defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..4de87f33bcb 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -3,57 +3,50 @@ layout: page title: About Us --- -We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). +We are a NUS based software development team. -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can contact us individually through our portfolios -## Project team +## ImmuniMate Project Team -### John Doe +### Alex Setyawan - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] - -* Role: Project Advisor - -### Jane Doe - - - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/alex-setyawan)] +[[portfolio](team/alex-setyawan)] * Role: Team Lead -* Responsibilities: UI +* Responsibilities: Project Manager, Back-end + -### Johnny Doe +### Tan Jovan - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[homepage](http:/jovantanyk.dev)] +[[github](https://github.com/jovantanyk)] +[[portfolio](team/jovantanyk.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Back-end, Documentation + -### Jean Doe +### Zhang Lanyu - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/laney0808)] [[portfolio](team/laney0808.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Front-end, Documentation, UI Designer -### James Doe +### Natalie Leong - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/NatLeong)] +[[portfolio](team/alex-setyawan)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Front-end, Documentation diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1b56bb5d31b..677d833f053 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -7,18 +7,14 @@ title: Developer Guide -------------------------------------------------------------------------------------------------------------------- -## **Acknowledgements** - -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} - --------------------------------------------------------------------------------------------------------------------- - ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- +
+ ## **Design**
@@ -51,7 +47,7 @@ The bulk of the app's work is done by the following four components: **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete S1234567X`. @@ -83,6 +79,8 @@ The `UI` component, * 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) @@ -91,7 +89,7 @@ Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete S1234567X")` API call as an example. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) @@ -100,39 +98,37 @@ The sequence diagram below illustrates the interactions within the `Logic` compo How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
+1. When `Logic` is called upon to execute a command, it is passed to an `ImmuniMateParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: 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 `ImmuniMateParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `CreateCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `CreateCommand`) which the `ImmuniMateParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `CreateCommandParser`, `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) - + + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +* stores the patient 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) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - -
- +
### Storage component @@ -147,101 +143,294 @@ The `Storage` component, ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. -------------------------------------------------------------------------------------------------------------------- +
+ ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### List all patients +#### Implementation -#### Proposed Implementation +The `list` feature allows users to view all patients in the system through the `list` command. This patient data is then displayed in the system for the user to have an overview of all patients. +The `list` command is facilitated by `ListCommand` which extends the `Command` classes, listing all instances in the `Model`. -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: +* `ListCommand#execute` is responsible for executing the command and listing all patients in the system. +* `Model#updateFilteredPersonList()` is called to make the UI display all patients in 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. +Step 1. `ListCommand#execute` is called by the `LogicManager`. The `ListCommand` calls `model.updateFilteredPersonList()` to update the filtered list of patients in the system. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Step 2. `Model#updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS)` is called to update the filtered list such that it shows all patients in the system. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Step 3. The `ListCommand` returns the appropriate `CommandResult` to indicate the success of the operation. -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. +### Help +#### Implementation -![UndoRedoState0](images/UndoRedoState0.png) +The `help` feature allows users to view the link to User Guide. This command is facilitated by `HelpCommand` which extends the `Command` classes. -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. +* `HelpCommand#execute` is responsible for executing the command and listing the link to the User Guide. -![UndoRedoState1](images/UndoRedoState1.png) +Step 1. Parser interprets the user's input and creates a new `HelpCommand` instance. -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 2. `HelpCommand#execute` is called by the `LogicManager`. The `HelpCommand` returns the appropriate `CommandResult`, which signals the UI to display the help window . -![UndoRedoState2](images/UndoRedoState2.png) +### Create new patient +#### Implementation -
: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`. +The `create` feature allows users to create a patient by providing input through a command with specific arguments. This patient data is then stored within the system for future reference. +The `create` command is facilitated by `CreateCommand` and `CreateCommandParser`. They extend the `Command` and `Parser` classes respectively, storing a new instance of `Person` in the `UniquePersonList`. +* `CreateCommandParser#parse` is responsible for parsing the user input and creating a new `CreateCommand` instance. +* `CreateCommand#execute` is responsible for executing the command and adding the new patient to the system. +* `ImmuniMate#addPerson(Person)` is called to add the patient to the internal list of patients. +* `UniquePersonList#add(Person)` is used to add the new patient to the system. -
+`ModelManager#addPerson(Person)` is called to add the patient to the system. It calls `ImmuniMate.addPerson(Person)` which calls `UniquePersonList#add(Person)` to add the patient to the internal list of patients. -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. +The command checks for duplicates in the system before adding the new patient. +* `Person#equals(Object)` is overridden to check if two patients are duplicates. +* `UniquePersonList#contains(Person)` is used to check if the patient already exists in the system's list of patients. +* `ImmuniMate#hasPerson(Person)` is used to check if the patient already exists in the system. +`ModelManager#hasPerson(Person)` is called to check if the patient already exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient already exists in the internal list of patients. -![UndoRedoState3](images/UndoRedoState3.png) +The creation of `Person` instance also rely on field classes, such as `Name`, `Nric`, `Phone`, `Address`, `DateOfBirth`, `Sex`, and `Status`. Optional field classes include `Email`, `Country`, `DateOfAdmission`, `BloodType`, `Allergies`, `Conditions`, `Symptoms`, and `Diagnosis`. -
: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. +Step 1. `CreateCommandParser` interprets the user's input, creates instances of fields which matches the input, and creates a new `CreateCommand` instance. -
+Step 2. The `CreateCommand#execute` is called by the `LogicManager`. The `CreateCommand` checks if the patient already exists in the system by calling `model.hasPerson(person)`. -The following sequence diagram shows how an undo operation goes through the `Logic` component: +Step 3. If there is no duplicate, the patient is added to the system by calling `model.addPerson(person)`. -![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png) +Step 4: After the patient is added, the `CreateCommand` returns the appropriate `CommandResult` to indicate the success of the operation. -
: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 sequence diagram shows how a create operation goes through the `Logic` component: +![CreateState1](images/CreateCommandLogic.png) +Similarly, the following sequence diagram shows how a create operation goes through the `Model` component: +![CreateState1](images/CreateCommandModel.png) -
+### Delete patient +#### Implementation -Similarly, how an undo operation goes through the `Model` component is shown below: +The `delete` feature allows users to delete a patient by providing NRIC through a command. This patient data is then removed from the system. +The `delete` command is facilitated by `DeleteCommand` and `DeleteCommandParser`. They extend the `Command` and `Parser` classes respectively, removing an instance of `Person` from the `UniquePersonList`. -![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) +* `DeleteCommandParser#parse` is responsible for parsing the user input and creating a new `DeleteCommand` instance. +* `DeleteCommand#execute` is responsible for executing the command and removing the patient from the system. +* `ImmuniMate#removePerson(Person)` is called to remove the patient from the internal list of patients. +* `UniquePersonList#remove(Person)` is used to remove the patient from the system. +`ModelManager#deletePerson(Person)` is called to remove the patient from the system. It calls `ImmuniMate.removePerson(Person)` which calls `UniquePersonList#remove(Person)` to remove the patient from the internal list of patients. +`DeleteCommand` checks if the patient exists in the system before removing the patient. +* `ModelManager#hasPerson(Person)` is called to check if the patient already exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient already exists in the internal list of patients. -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. +Step 1. `DeleteCommandParser` interprets the user's input for NRIC, and creates a new `DeleteCommand` instance. -
: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. +Step 2. The `DeleteCommand#execute` is called by the `LogicManager`. The `DeleteCommand` checks if the patient exists in the system by calling `model.hasPerson(person)`. -
+Step 3. If the patient exists, the patient is removed from the system by calling `model.deletePerson(person)`. + +Step 4: After the patient is removed, the `DeleteCommand` returns the appropriate `CommandResult` to indicate the success of the operation. + +The following sequence diagram shows how a delete operation goes through the `Logic` and `Model` component: +![DeleteState1](images/DeleteSequenceDiagram.png) + +### Delete patient information +#### Implementation + +The `deleteinfo` command allows users to delete a patient's particular field of information by providing NRIC and the field to be deleted through a command. +This `deleteinfo` command is facilitated by `DeleteInfoCommand` and `DeleteInfoCommandParser`. They extend the `Command` and `Parser` classes respectively, removing a particular field of information from the `Person` object. + +* `DeleteInfoCommandParser#parse` is responsible for parsing the user input and creating a new `DeleteInfoCommand` instance. +* `DeleteInfoCommand#execute` is responsible for executing the command and removing the field of information from the patient. +* `Model#getFilteredPersonList()` is called to get the list of patients in the system. +* `Observablelist#filtered(Predicate)` is called to obtain `Person` object of patient with specified NRIC. +* `Observablelist#get(int)` is called to obtain `Person` object in the filtered list. +* `Person#setField` where `Field` refers to whichever field specified to be deleted, is responsible for removing the field of information from the patient. + `DeleteInfoCommand` checks if the patient exists in the system before removing the field of information. +* `ModelManager#hasPerson(Person)` is called to check if the patient already exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient already exists in the internal list of patients. + +Step 1. `DeleteInfoCommandParser` interprets the user's input for NRIC and the fields to be deleted, and creates a new `DeleteInfoCommand` instance. + +Step 2. The `DeleteInfoCommand#execute` is called by the `LogicManager`. The `DeleteInfoCommand` checks if the patient exists in the system by calling `model.hasPerson(person)`. + +Step 3. If the patient exists, the `DeleteInfoCommand` calls `model.getFilteredPersonList().filtered().get()` to get the specified patients in the system. + +Step 4. `DeleteInfoCommand#execute` check which fields are to be deleted, and remove the field of information using `Person#setField(null)`. Where `Field` is the field to be deleted. + +Step 5: After the field of information is removed, the `DeleteInfoCommand` returns the appropriate `CommandResult` to indicate the success of the operation. + +The following sequence diagram shows how a `deleteinfo` operation goes through the `Logic` component: +![DeleteInfoState1](images/DeleteInfoSequenceDiagram.png) + +The sequence diagram for how the `deleteinfo` operation goes through the `Model` Component is as the following: +![DeleteInfoState1](images/DeleteInfoModelDiagram.png) + +### Read a patient's information +#### Implementation +The `read` feature allows users to read a patient profile by providing NRIC through a command. This patient data is then displayed. +The `read` command is facilitated by `ReadCommand` and `ReadCommandParser`. They extend the `Command` and `Parser` classes respectively, displaying patient profile from an instance of `Person` from the `UniquePersonList`. + +* `ReadCommandParser#parse` is responsible for parsing the user input and creating a new `ReadCommand` instance. +* `ReadCommand#execute` is responsible for executing the command and displaying the patient profile from the system. + `ReadCommand` checks if the patient exists in the system before displaying patient profile. +* `ModelManager#hasPerson(Person)` is called to check if the patient exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient already exists in the internal list of patients. +* `Model#updateFilteredPersonList(Predicate)` and is called to update the list to be of patient with specified NRIC in the system. +* `Model#getFilteredPersonList()` is called to get the list of patient with specified NRIC in the system. +* `Observablelist#get(int)` is called to obtain `Person` object of patient with speicified NRIC. + +Step 1. `ReadCommandParser` interprets the user's input for NRIC, and creates a new `ReadCommand` instance. + +Step 2. The `ReadCommand#execute` is called by the `LogicManager`. The `ReadCommand` checks if the patient exists in the system by calling `model.hasPerson(person)`. + +Step 3. If the patient exists, the patient is obtained from the system by calling `model.updateFilteredPersonList(person)`, followed by calling `model.getFilteredPersonList()` and `Observablelist#get(int)`. + +Step 4: After the patient is obtained, the `ReadCommand` formats the patient profile by calling `Messages.format(person)` and returns the appropriate `CommandResult` to indicate the success of the operation. + +The following sequence diagram shows how a `read` operation goes through the `Logic` component: +![ReadState1](images/ReadCommandSequenceDiagram.png) + +How a `read` operation goes through the `Model` component is shown below: +![ReadState2](images/ReadCommandModelDiagram.png) + +### Find patient +#### Implementation + +The `find` feature lets users find patients with certain values in various fields, namely `name`, `address` and `condition`. +After a command with specific arguments as input, the patient list is filtered and the resultant list is shown. + +The `find` command is facilitated by `FindCommand` and `FindCommandParser`, extending the `Command` and `Parser` classes respectively. +* `FindCommandParser#parse` is responsible for parsing the user input and creating a new `FindCommand` instance. +* `FindCommand#execute` is responsible for executing the command, calling `ModelManager#updateFilteredPersonList(Predicate)` to find patients with the given keywords in the given field. + +Step 1. `FindCommandParser` interprets the user's input, determine which field the user wishes to find patients by, and creates a new `FindCommand` instance. + +Step 2. The `FindCommand#execute` is called by the `LogicManager`. + +Step 3. This in turn calls `model.updateFilteredPersonList(Predicate)`, which identifies patients having any of the keywords in `Predicate` in the given field. + +Step 4: The `FindCommand` returns the appropriate `CommandResult` to indicate the success of the operation. + +The following sequence diagram shows how a `find` operation goes through the `Logic` and `Model` components: +![ReadState1](images/FindLogicModelDiagram.png) + +### Update patient fields +#### Implementation +Given below is an example usage scenario and how the update mechanism behaves at each step. + +Step 1. The user launches the application for the first time. + +Step 2. The user types `update T0123456A a/35 Bishan Road, #10-40 con/myopia` command to update the address and condition fields in the profile of the person with `Nric` T0123456A in the address book. This calls `immuniMateParser.parseCommand()`, which separates the user input into the command `update` and the rest of the arguments. + +Step 3. As the command `update` matches `UpdateCommand.COMMAND_WORD`, `UpdateCommandParser().parse()` is called, taking in the rest of the arguments. Here, the arguments are checked if they are null values, then passed into `ArgumentTokenizer.tokenize()`, where they are separated into `Nric` and other provided fields by finding their respective prefixes, and stored in an `ArgumentMultimap`. + +Step 4. Still in `UpdateCommandParser().parse()`, checks are then done to verify the validity of the `Nric` and that no duplicate prefixes are found. A new `UpdatePersonDescriptor` object is then created to store the fields present in `ArgumentMultimap`. + +Step 5. At the end of `UpdateCommandParser().parse()`, a new `UpdateCommand` instance is created with the `Nric` and `UpdatePersonDescriptor` as arguments. `UpdateCommand.execute()` is then called, taking in the ImmuniMate `model` as an argument. + +Step 6. `model.getFilteredPersonsList()` retrieves the list of `Person`s stored, and a check is done to see if ImmuniMate has a `Person` with the given `Nric`. This `Person` is then retrieved from the list, while a new `Person` object is instantiated, with the `Person` and `UpdatePersonDescriptor` as arguments, representing the retrieved `Person` object with fields updated. + +Step 7. `model.setPerson()` then replaces the retrieved `Person` object with the new `Person` object with fields updated, taking in both `Person` objects as arguments. The `model` is then saved into `storage`. + +The following sequence diagram shows how an `update` operation goes through the `Logic` components: +![ReadState1](images/UpdateLogicDiagram.png) + +How an `update` operation goes through the `Model` component is shown below: +![ReadState2](images/UpdateModelDiagram.png) + +### Add patient visit +#### Implementation +The `addvisit` feature allows users to add a patient visit by providing input through a command with specific arguments. This visit is then stored within the system for future reference. +The `addvisit` command is facilitated by `AddVisitCommand` and `AddVisitCommandParser`. They extend the `Command` and `Parser` classes respectively, storing a new instance of `Visit` in the `UniqueVisitList`. +* `AddVisitCommandParser#parse` is responsible for parsing the user input and creating a new `AddVisitCommand` instance. +* `AddVisitCommand#execute` is responsible for executing the command and adding the new visit to the system. +* `ImmuniMate#addVisit(Visit)` is called to add the visit to the internal list of visits. +* `UniquePersonList#add(Visit)` is used to add the new visit to the system. + `ModelManager#addVisit(Visit)` is called to add the visit to the system. It calls `ImmuniMate.addVisit(Visit)` which calls `UniqueVisitList#add(Visit)` to add the visit to the internal list of visits. + The command checks if the patient exists in the system before adding the new visit of a patient. +* `Person#equals(Object)` is overridden to check if two patients are the same person. +* `UniquePersonList#contains(Person)` is used to check if the patient exists in the system's list of patients. +* `ImmuniMate#hasPerson(Person)` is used to check if the patient exists in the system. + `ModelManager#hasPerson(Person)` is called to check if the patient exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient exists in the internal list of patients. + The command checks for duplicates in the system before adding the new visit. +* `Visit#equals(Object)` is overridden to check if two visits are duplicates. +* `UniquePersonList#contains(Visit)` is used to check if the visit already exists in the system's list of visits. +* `ImmuniMate#hasVisit(Visit)` is used to check if the visit already exists in the system. + `ModelManager#hasVisit(Visit)` is called to check if the visit already exists in the system. It calls `ImmuniMate.hasVisit(Visit)` which calls `UniqueVisitList#contains(Visit)` to check if the visit already exists in the internal list of visits. + +The creation of `Visit` instance also rely on field classes`NRIC`, `Symptoms`,`Diagnosis`, `Status` and `DateOfVisit`. + +Step 1. `AddVisitCommandParser` interprets the user's input, creates instances of fields which matches the input, and creates a new `AddVisitCommand` instance. + +Step 2. The `AddVisitCommand#execute` is called by the `LogicManager`. The `AddVisitCommand` checks if the patient already exists in the system by calling `model.hasPerson(person)`. + +Step 3. If the patient exists, the `AddVisitCommand` checks if the visit already exists in the system by calling `model.hasVisit(visit)`. + +Step 4. If the visit does not exist, the visit is added to the system by calling `model.addVisit(visit)`. + +Step 4: After the visit is added, the `AddVisitCommand` returns the appropriate `CommandResult` to indicate the success of the operation. + +The following sequence diagram shows how a addvisit operation goes through the `Logic` component: +![AddVisitState1](images/AddVisitCommandLogic.png) +Similarly, the following sequence diagram shows how a addvisit operation goes through the `Model` component: +![addVisitCommandState1](images/AddVisitCommandModel.png) + + +### Check a patient's visit history +#### Implementation +The `check` feature allows users to check the visit history of a patient by providing NRIC through a command. This patient visit history is then displayed. +The `check` command is facilitated by `CheckCommand` and `CheckCommandParser`. They extend the `Command` and `Parser` classes respectively, displaying patient visit history from list of `Visit` from the `UniqueVisitList`. + +* `CheckCommandParser#parse` is responsible for parsing the user input and creating a new `CheckCommand` instance. +* `CheckCommand#execute` is responsible for executing the command and displaying the patient visit history from the system. + `CheckCommand` checks if the patient exists in the system before displaying patient visit history. +* `ModelManager#hasPerson(Person)` is called to check if the patient exists in the system. It calls `ImmuniMate.hasPerson(Person)` which calls `UniquePersonList#contains(Person)` to check if the patient already exists in the internal list of patients. +* `Model#updateFilteredPersonList(Predicate)` is called to get the list of patient with specified NRIC in the system. +* `Model#updateFilteredVisitList(Predicate)` is called to get the list of visits with specified NRIC in the system. + +Step 1. `CheckCommandParser` interprets the user's input for NRIC, and creates a new `CheckCommand` instance. + +Step 2. The `CheckCommand#execute` is called by the `LogicManager`. The `CheckCommand` checks if the patient exists in the system by calling `model.hasPerson(person)`. + +Step 3. If the patient exists, the patient is obtained from the system by calling `model.updateFilteredPersonList(pred)`, followed by calling `model.getFilteredPersonList()` and `Observablelist#get(int)`. + +Step 4: Patient visit history is obtained from the system by calling `model.updateFilteredVisitList(pred)`, followed by `model.getFilteredVisitList()`. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5: After the patient visit history is obtained, the `CheckCommand` formats the patient visit history by calling `Messages.formatCheck(visit)` and returns the appropriate `CommandResult` to indicate the success of the operation. -![UndoRedoState4](images/UndoRedoState4.png) +The following sequence diagram shows how a delete operation goes through the `Logic` component: +![ReadState1](images/CheckCommandSequenceDiagram.png) +How a check operation goes through the `Model` component is shown below: +![ReadState2](images/CheckCommandModelDiagram.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +### Cluster Identification +#### Implementation -![UndoRedoState5](images/UndoRedoState5.png) +The `cluster` feature tells users if a certain location has a certain number of patients unwell with an illness. +Provided an integer, location and disease as inputs, it finds unwell patients having an address with the given location as a substring, and diagnosis with the given illness as a substring, and informs the user if the number of those patients is at least that integer. +The `cluster` command is facilitated by `ClusterCommand` and `ClusterCommandParser`, extending the `Command` and `Parser` classes respectively, storing a new instance of `Person` in the `UniquePersonList`. -The following activity diagram summarizes what happens when a user executes a new command: +* `ClusterCommandParser#parse` is responsible for parsing the user input and creating a new `ClusterCommand` instance. +* `ClusterCommand#execute` is responsible for executing the command and separating the inputs into their categories (cluster size, address and illness). It calls `ModelManager#updateFilteredPersonList(Predicate)` to find patients which satisfy the given criteria. - +Step 1. `ClusterCommandParser` interprets the user's input, and separates it into its constituent categories, and creates a new `ClusterCommand` instance. -#### Design considerations: +Step 2. The `ClusterCommand#execute` is called by the `LogicManager`. -**Aspect: How undo & redo executes:** +Step 3. This in turn calls `model.updateFilteredPersonList(Predicate)`, takes the location and illness keywords in `Predicate`, and finds all the patients which have them as substrings in their addresses and diagnoses respectively. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +Step 4. This also calls `model.getFilteredPersonList().size()` to obtain the size of the patient group satisfying the given criteria. -* **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. +Step 5: The `ClusterCommand` returns the appropriate `CommandResult` to indicate the success of the operation. -_{more aspects and alternatives to be added}_ +The following sequence diagram shows how a `cluster` operation goes through the `Logic` and `Model` components: +![ReadState1](images/ClusterLogicModelDiagram.png) -### \[Proposed\] Data archiving +### Exit the app +#### Implementation -_{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- @@ -256,80 +445,395 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- +
+ ## **Appendix: Requirements** ### Product scope **Target user profile**: -* has a need to manage a significant number of contacts +* has a need to manage a significant number of patient information * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps +* works at a gp clinic or similar environments -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: As the number of patients a General Practitioner grows, information management might prove complex, especially so for personal data. ImmuniMate offers a way to record comprehensive information about every patient, while ensuring timely updates and avoiding duplications/contradictions. It also seeks to establish links between patient for contact tracing and finding potential infectious clusters. ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-------------------|-------------------------------------------------|------------------------------------------------------------------------------| +| `* * *` | New user | see usage instructions | refer to instructions when I forget how to use the App | +| `* * *` | Healthcare Worker | create a new patient profile | add new patient to database | +| `* * *` | Healthcare Worker | delete a patient | remove wrong or obselete patient profile from the database | +| `* * *` | Healthcare Worker | delete patient's information | remove patient information that is known to be incorrect | +| `* * *` | Healthcare Worker | read a patient's information by NRIC | locate details of persons without having to go through the entire list | +| `* * *` | Healthcare Worker | update a person's details | keep the details up to date | +| `* * *` | Healthcare Worker | find a patient by matching criteria | Find a list of patients from with a certain conditio or address | +| `* * *` | Healthcare Worker | find a patient by name | find a patient in the situation when their NRIC is not immediately available | +| `* * *` | Healthcare Worker | record visits made by patients | record the change in health condition of the patients across time | +| `* *` | Healthcare Worker | highlight contradicting information and entries | minimize the mistakes in entries | +| `* *` | Healthcare Worker | see the history of visits made by a patient | understand the past health condition of a patient | +| `* *` | Healthcare Worker | be able to tap different contact methods | make sure important information is sent | +| `* *` | Healthcare Worker | status of health of a patient | give appropriate treatment | +| `* *` | Healthcare Worker | see clusters of infected patients | understand which areas are at high risk of infection | +| `*` | Healthcare Worker | find the person who first spread the disease | better understand the disease | +| `*` | Healthcare Worker | sort persons by name | locate a person easily | +| `*` | Healthcare Worker | hide private contact details | minimize chance of someone else seeing them by accident | +| `*` | Healthcare Worker | be able to resolve duplicate information | correct wrong inputs | +| `*` | Healthcare Worker | see the close contacts of a patient | see the links between infected patients | ### 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 **IMS** is the `ImmuniMate system` and the **Healthcare Worker** is the `user`, unless specified otherwise) + +**Use Case: UC01 - Create Patient Profile** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker creates a new patient profile in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** New patient profile is successfully created in the IMS. +- **MSS:** + 1. Healthcare worker choose to create a new patient profile. + 2. IMS requests the necessary details for the new patient profile (name, NRIC, date of birth, sex, phone number, address, email, country of nationality, date of admission, blood type, allergies). + 3. Healthcare worker enters the patient's details. + 4. IMS validates the entered data. + 5. IMS adds the new patient profile to the database. +- **Extensions:** + + 3a. IMS detects a conflict in the entered data (user existing). + + 3a1. IMS shows the conflicting existing entry + 3a2. IMS requests for the correct data. + 3a3. Healthcare Worker enters new data. + Steps 3a1-3a3 are repeated until the data entered are correct, or the user cancels the action. + Use case resumes from step 4. + + 3b. IMS detects an error in the entered data. + + 3b1. IMS requests for the correct data. + 3b2. Healthcare Worker enters new data. + Steps 3b1-3b2 are repeated until the data entered are correct. + Use case resumes from step 4. + + *a. At any time, Healthcare Worker chooses to cancel creating the patient profile. + + *a1. IMS requests confirmation to cancel. + *a2. Healthcare Worker confirms the cancellation. + Use case ends. + + +--- + +**Use Case: UC02 - Read Patient Profile** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker reads patient profile in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** Existing patient profile in the IMS is successfully displayed. +- **MSS:** + 1. Healthcare worker choose to read a patient profile. + 2. IMS requests the necessary detail for reading patient profile (NRIC). + 3. Healthcare worker enters the patient's details. + 4. IMS validates the entered data. + 5. IMS displays the patient profile to the database. +- **Extensions:** + + 3a. IMS detects an error in the entered data (user does not exist). + + 3a1. IMS shows the conflicting existing entry + 3a2. IMS requests for the correct data. + 3a3. Healthcare Worker enters new data. + Steps 3a1-3a3 are repeated until the data entered are correct, or the user cancels the action. + Use case resumes from step 4. + + 3b. IMS detects an error in the entered data (wrong NRIC format). + + 3b1. IMS requests for the correct data. + 3b2. Healthcare Worker enters new data. + Steps 3b1-3b2 are repeated until the data entered are correct. + Use case resumes from step 4. + + *a. At any time, Healthcare Worker chooses to cancel creating the patient profile. + + *a1. IMS requests confirmation to cancel. + *a2. Healthcare Worker confirms the cancellation. + Use case ends. + + +--- + +**Use Case: UC03 - Find Patient Information** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker searches for specific patient information in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** Relevant patient information is displayed for the healthcare worker to view. +- **Basic Flow:** + 1. Healthcare worker chooses to find patient information meeting specified criteria. + 2. IMS searches for and displays the relevant patients. +- **Extensions:** + 2a. IMS detects an error in the entered data. + + - 2a1. IMS requests for the correct data. + - 2a2. Healthcare Worker enters new data. + - Steps 2a1-2a2 are repeated until the data entered are correct. Use case resumes from step 3. + +--- + +**Use Case: UC04 - Update Patient Information** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker updates a patient's information in the IMS. +- **Preconditions:** Healthcare worker has launched the system and has selected the patient whose information needs to be updated. +- **Guarantees:** Patient's information is successfully updated in the IMS. +- **Basic Flow:** + 1. Healthcare worker chooses to update a certain patient’s certain information. + 2. IMS validates the new content. + 3. IMS updates the patient's information in the database. +- **Extensions:** + + 2a. IMS detects an error in the entered data. -**Use case: Delete a person** + 2a1. IMS requests for the correct data. + 2a2. Healthcare Worker enters new data. + Steps 2a1-2a2 are repeated until the data entered are correct. + Use case resumes from step 3. -**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 +--- + +**Use Case: UC05 - Delete Patient Profile** + +- **Actor:** Healthcare worker +- **Description:** Healthcare worker deletes a patient's record from the IMS. +- **Preconditions:** Healthcare worker has opened the app and has selected the patient whose record needs to be deleted. +- **Guarantees:** Patient's record is successfully deleted from the IMS. +- **MSS:** + 1. Healthcare worker choose to delete a specified patient’s record. + 2. IMS validates the NRIC and deletes the patient's record from the database. +- **Extensions:** + + 2a. IMS cannot find the patient specified. + - 2a1. IMS requests for the correct NRIC. + - 2a2. Healthcare worker enters new NRIC. + - Steps 2a1-2a2 are repeated until the data entered are correct or Healthcare worker cancels the action. Use case resumes from step 3. + +--- + +**Use Case: UC06 - Delete Patient Information** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker deletes specific information from a patient's record in the IMS. +- **Preconditions:** Healthcare worker has launched the system and has selected the patient whose information needs to be deleted. +- **Guarantees:** Specified information is successfully deleted from the patient's record in the IMS. +- **MSS:** + 1. Healthcare worker chooses to delete certain fields of a certain patient's profile. + 2. IMS validates the information to be deleted and deletes the specified information from the patient's record in the database. +- **Extensions:** + + 2a. IMS cannot find the patient specified. + - 2a1. IMS requests for the correct NRIC. + - 2a2. Healthcare worker enters new NRIC. + - Steps 2a1-2a2 are repeated until the data entered are correct or Healthcare worker cancels the action. Use case resumes from step 3. + + 2b. IMS cannot find the specified information. + + - 2b1. IMS alerts healthcare worker that the specified information is not found. + - 2b2. Healthcare worker enters new field. + - Steps 2b1-2b2 are repeated until the data entered are correct or Healthcare worker cancels the action. Use case resumes from step 3. + + 2c. Healthcare worker chooses to delete a mandatory field. + + - 2c1. IMS alerts healthcare worker that mandatory field cannot be deleted. + - 2c2. Healthcare worker enters new field. + - Steps 2c1-2c2 are repeated until the data entered are correct or Healthcare worker cancels the action. Use case resumes from step 3. + + +--- + +**Use Case: UC07 - Add Patient Visit** + + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker adds a visit record for a patient in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** A new visit record is successfully added for the patient in the IMS. +- **Main Success Scenario (MSS):** + 1. Healthcare worker chooses to add a new patient visit. + 2. IMS requests the necessary details for the visit (NRIC, date of visit, diagnosis, symptoms, status). + 3. Healthcare worker enters the required information for the visit. + 4. IMS validates the entered data. + 5. IMS adds the visit record to the patient's profile in the database. + 6. IMS confirms that the visit has been added successfully. + 7. Use case ends. + +- **Extensions:** + + 3a. IMS detects an error in the entered data (patient does not exist). + - 3a1. IMS notifies Healthcare Worker that the patient does not exist. + - 3a2. Healthcare Worker opts to create a new patient profile or re-enters correct data. + - Steps 3a1-3a2 are repeated until valid data are entered, or the user cancels the action. + - Use case resumes from step 4 if valid data are entered. + + 3b. IMS detects an error in the entered data (e.g., incorrect NRIC format, invalid date of visit). + - 3b1. IMS requests for the correct data. + - 3b2. Healthcare Worker enters new data. + - Steps 3b1-3b2 are repeated until the data entered are correct. + - Use case resumes from step 4. + + 3c. IMS detects missing mandatory fields (e.g., NRIC, date of visit). + - 3c1. IMS displays an error message indicating which fields are missing. + - 3c2. Healthcare Worker provides the missing information. + - Steps 3c1-3c2 are repeated until all required data are provided. + - Use case resumes from step 4. + + *a. At any time, Healthcare Worker chooses to cancel the addition of the patient visit. + - *a1. IMS requests confirmation to cancel. + - *a2. Healthcare Worker confirms the cancellation. + - Use case ends. - Use case ends. +--- + +**Use Case: UC08 - Check Patient Visit History** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker checks patient visit history in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** Existing patient visit history in the IMS is successfully displayed. +- **MSS:** + 1. Healthcare worker choose to check a patient visit history. + 2. IMS requests the necessary detail for checking patient visit history (NRIC). + 3. Healthcare worker enters the patient's details. + 4. IMS validates the entered data. + 5. IMS displays the patient profile to the database. +- **Extensions:** + + 3a. IMS detects an error in the entered data (user does not exist). + + 3a1. IMS shows the conflicting existing entry + 3a2. IMS requests for the correct data. + 3a3. Healthcare Worker enters new data. + Steps 3a1-3a3 are repeated until the data entered are correct, or the user cancels the action. + Use case resumes from step 4. -**Extensions** + 3b. IMS detects an error in the entered data (wrong NRIC format). -* 2a. The list is empty. + 3b1. IMS requests for the correct data. + 3b2. Healthcare Worker enters new data. + Steps 3b1-3b2 are repeated until the data entered are correct. + Use case resumes from step 4. + *a. At any time, Healthcare Worker chooses to cancel creating the patient profile. + + *a1. IMS requests confirmation to cancel. + *a2. Healthcare Worker confirms the cancellation. Use case ends. -* 3a. The given index is invalid. +--- - * 3a1. AddressBook shows an error message. +**Use Case: UC09 - Finding Clusters** + +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker tries to identify disease clusters through data in the ImmuniMate System. +- **Preconditions:** Healthcare worker has logged into the system. +- **Guarantees:** There is at least 1 patient profile saved in IMS. +- **MSS:** + 1. Healthcare worker wants to find disease clusters/potential disease clusters. + 2. IMS requests Healthcare Worker for relevant details. + 3. Healthcare worker enters integer, location and illness. + 4. IMS validates the entered data and displays all patients at that location unwell with the illness. +- **Extensions:** + + 3a. IMS detects an error in the entered data (invalid cluster size). + + * 3a1. IMS shows the conflicting existing entry + * 3a2. IMS requests for the correct data. + * 3a3. Healthcare Worker enters data with valid cluster size.
+ Steps 3a1-3a3 are repeated until the data entered are correct, or the user cancels the action. + Use case resumes from step 4. + + 3b. IMS detects an error in the entered data (empty location). + + * 3b1. IMS requests for the correct data. + * 3b2. Healthcare Worker enters data with valid location.
+ Steps 3b1-3b2 are repeated until the data entered are correct. + Use case resumes from step 4. + +--- + +**Use Case: UC10 - List all patients** - Use case resumes at step 2. +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker lists all patients in the IMS. +- **Preconditions:** Healthcare worker has launched the system. The data file is not corrupted. +- **Guarantees:** Existing patients in the IMS are successfully displayed. +- **MSS:** + 1. Healthcare worker choose to list all patients. + 2. IMS displays all patients in the database. -*{More to be added}* +--- +**Use Case: UC11 - Access help** +- **Actor:** Healthcare Worker +- **Description:** Healthcare worker accesses the help guide in the IMS. +- **Preconditions:** Healthcare worker has launched the system. +- **Guarantees:** The help guide is successfully displayed. +- **MSS:** + 1. Healthcare worker chooses to access the help guide. + 2. IMS displays the help guide. ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 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. - -*{More to be added}* +2. Should be able to hold up to 10000 persons without a noticeable sluggishness in performance for typical usage. +3. Should have high data persistency. +4. Should work on both 32-bit and 64-bit environments. +5. Should be easily downloaded from websites/app stores, and need no prior setup besides downloading Java 11. +6. Should not exceed 100MB in storage space. +7. Data collection on the app should adhere to the Personal Data Protection Act (PDPA). +8. Unimplemented fields should be added easily. +9. Should be convenient to expand storage capacity of the app when needed. +10. Should use automated testing. +11. Should respond to queries within 1 second. +12. Project should adhere to a schedule to deliver new features fortnightly. +13. Project should aim to solve bugs found in one version by the next version. +14. Should be usable by doctors/nurses/receptionists with limited guidance. +15. Should be faster to use by typing queries than using the mouse. ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +1. **Name**: The name of the patient. Case-sensitive alphabetical characters with spaces, capped at 2,000,000,000 characters. +2. **NRIC**: National Registration Identity Card number, follows Singapore NRIC format. 9 characters. First character can be any of S, T, followed by 7 digits, and the last character is an alphabet. NRIC is case-insensitive. +3. **Date of Birth (DOB)**: The patient's date of birth, in the format `yyyy-MM-dd`. +4. **Sex**: The biological sex of the patient, limited to `M` or `F`. +5. **Phone Number**: The contact number of the patient, limited to 8 digits. +6. **Address**: The home address of the patient. Any text except blank and empty text is accepted. +7. **Email**: The email address of the patient, follows a valid format: `@.com`. Case-sensitive. +8. **Country of Nationality**: The country name of the patient's nationality. Any text except blank and empty text is accepted. +9. **Date of Admission (DOA)**: The date when the patient was admitted for the first visit, in the format `yyyy-MM-dd`. +10. **Blood Type**: The blood type of the patient. accepts any of `A+`, `A-`, `B+`, `B-`, `AB+`, `AB-`, `O+`, `O-`. +11. **Allergies**: Any allergies the patient may have. Any text except blank and empty text is accepted. +12. **Conditions**: Any prior medical conditions of the patient. Any text except blank and empty text is accepted. +13. **Symptoms**: The latest symptoms experienced by the patient. Any text except blank and empty text is accepted. +14. **Diagnosis**: The latest diagnosis of the patient's condition. Any text except blank and empty text is accepted. +15. **Status**: The current health status of the patient, can be `HEALTHY` (green), `PENDING` (yellow), or `UNHEALTHY` (red). `PENDING` means the patient still need upcoming diagnosis to determine the current health status. +16. **Date of Visit**: The date of the patient's current visit, in the format `yyyy-MM-dd`. +17. **Fields**: The fields of the patient's information, such as name, status, contact. +17. **Cluster**: A group of patients who are infected by the same disease. +18. **Patient Visit**: A record of a patient's one specific visit to the clinic, including the date of visit, symptoms, diagnosis, and status. +19. **Patient History**: A collection of all the visits by a patient. +20. **Patient Profile**: A collection of all the information about a patient, including the patient's name, `Nric`, `Phone`, `Address`, `Email`, `Country`. -------------------------------------------------------------------------------------------------------------------- +
+ ## **Appendix: Instructions for manual testing** Given below are instructions to test the app manually. @@ -343,40 +847,186 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1.1 Download the jar file and copy into an empty folder. - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1.2 Run `java -jar immuniMate.jar` for the jar file Expected: Shows the GUI with a set of sample contacts. -1. Saving window preferences +2. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 2.1 Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 2.2 Re-launch the app by running `java -jar ImmuniMate.jar` again.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +### Adding a person + +1. Adding a person while all persons are being shown. + 1. Prerequisites: + 1. List all persons using the `list` command. Multiple persons in the list. + 1. The person with NRIC `S1234567A` is not already created in the system. + 1. The person with NRIC `T0234567C` is already created in the system. + 1. Test case: `create n/Alice Tan ic/S1234567A dob/1990-01-01 hp/12345678 a/123, Jurong West Ave 6, #08-111 s/F st/PENDING`
+ Expected: A new person is added to the list. The result panel shows the details of the new person. + 1. Test case: `create n/Bob Tan ic/T0234567C dob/1990-01-01 hp/12345678 a/123, Jurong West Ave 6, #08-111 s/M st/PENDING`
+ Expected: A new person is not added to the list. The result panel shows an error message, indicating that the person already exists in the system. + 1. Test case: `create n/Charlie Tan ic/S1234567A`
+ Expected: A new person is not added to the list. The result panel shows an error message, indicating that the command format is invalid. +2. Adding a person while only some persons are being shown + 1. Prerequisites: + 1. Show only 1 person's details using the `find n/alex` command. One person is shown in the list. + 1. Test case: `create n/Bob Tan ic/T0234567C dob/1990-01-01 hp/12345678 a/123, Jurong West Ave 6, #08-111 s/M st/PENDING`
+ Expected: A new person is not added to the list. The result panel shows an error message, indicating that the person already exists in the system. + +### Reading a person +1. Reading a person while all persons are being shown. + 1. Prerequisites: + 1. List all persons using the `list` command. Multiple persons in the list. + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + + 1. Test case: `read S1234567A`
+ Expected: Details of the read patient shown in result panel. The list panel shows the read person. + + 1. Test case: `read S9876543N`
+ Expected: No person is read. Error details shown. + + 1. Other incorrect read commands to try: `read`, `read 0`, `...` (where the input for NRIC field does not follow format for NRIC)
+ Expected: Similar to previous. + 2. +2. Reading a person while only some persons are being shown + 1. Prerequisites: + 2. Show only 1 person's details using the `find n/alex` command. One person is shown in the list. + 3. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. Test case: `read S1234567A`
+ Expected: The result panel shows the details of the read person. The list panel shows the read person. ### Deleting a person 1. Deleting a person while all persons are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: + 1. List all persons using the `list` command. Multiple persons in the list. + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1. Test case: `delete S9234568N`
+ Expected: First patient is deleted from the list. Details of the deleted patient shown in the status message. Timestamp in the status bar is updated. - 1. Test case: `delete 0`
+ 1. Test case: `delete S9876543N`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Other incorrect delete commands to try: `delete`, `delete 0`, `...` (where the input for NRIC field does not follow format for NRIC)
Expected: Similar to previous. - -1. _{ more test cases …​ }_ - -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ +2. Deleting a person while only some persons are being shown + 1. Prerequisites: + 1. Show only 1 person's details using the `find n/alex` command. One person is shown in the list. + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. Test case: `delete S1234567A`
+ Expected: The person is deleted from the list. The result panel shows the details of the deleted person. The list panel shows a full list of patients. + +### Updating a person +1. Updating a existing person's updatable fields + 1. Prerequisites: + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. Test case: `update S1234567A a/35 Bishan Road, #10-40 con/myopia`
+ Expected: The person's address is updated to `35 Bishan Road, #10-40` and condition is updated to `myopia`. The result panel shows the updated person's details. + 2. Test case: `update S1234567A a/35 Bishan Road, #10-40 con/`
+ Expected: The person's both fields are not updated. The result panel shows an error message, indicating that the argument for condition is invalid. + 3. Test case: `update S1234567A`
+ Expected: The person is not updated at all. The result panel shows an error message, indicating that the command format is invalid. +2. Updating a non-existing person + 1. Prerequisites: + 1. The person with NRIC `S1234568A` does not exist in the system. + 2. Test case: `update S1234568A a/35 Bishan Road, #10-40 con/myopia`
+ Expected: The person is not updated at all. The result panel shows an error message, indicating that the person does not exist in the system. +3. Updating a person's non-updatable fields + 1. Prerequisites: + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. Test case: `update S1234567A a/35 Bishan Road, #10-40 con/myopia ic/S1234568A`
+ Expected: The person's address is updated to `35 Bishan Road, #10-40` and condition is updated to `myopia`. The NRIC is not updated. + +### Finding a person + +1. Finding a person by name, condition or address + 1. Prerequisites: + 1. The person with name `Alex Yeoh` exists in the system. + 1. The person with condition `diabetes` exists in the system. + 1. The person with address `123, Jurong West Ave 6, #08-111` exists in the system. + 1. Test case: `find n/Alex Yeoh`
+ Expected: The person with name `Alex Yeoh` is shown in the list. The result panel shows the details of the person. + 1. Test case: `find con/diabetes`
+ Expected: The person with condition `diabetes` is shown in the list. The result panel shows the details of the person. + 1. Test case: `find a/123, Jurong West Ave 6, #08-111`
+ Expected: The person with address `123, Jurong West Ave 6, #08-111` is shown in the list. The result panel shows the details of the person. +2. Finding a person by NRIC + 1. Test case: `find n/S1234567A`
+ Expected: The person with NRIC `S1234567A` is not shown in the list. The result panel shows an error message, indicating that the command format is invalid. + +### Deleting a person's information + +1. Deleting a person's optional fields + 1. Prerequisites: + 1. The person with NRIC `S1234567A` exists in the system. + 2. The person with NRIC `S1234568A` does not exist in the system. + 1. Test case: `deleteinfo S1234567A a/`
+ Expected: The person's address becomes null. The result panel shows the updated person's details. + 1. Test case: `deleteinfo S1234567A a/ con/`
+ Expected: The person's address and condition become null. The result panel shows the updated person's details. + 1. Test case: `deleteinfo S1234567A`
+ Expected: The person's information is not deleted. The result panel shows an error message, indicating that the command format is invalid. + 1. Test case: `deleteinfo S1234568A e/`
+ Expected: The person's name is not deleted. The result panel shows an error message, indicating that the person does not exist in the system. +2. Deleting a person's mandatory fields + 1. Prerequisites: + 1. The person with NRIC `S1234567A` exists in the system. + 1. Test case: `deleteinfo S1234567A n/`
+ Expected: The person's name is not deleted. The result panel shows an error message, indicating that the name field cannot be deleted. + +### Adding a person's visit +1. Adding a person's visit while all persons are being shown + 1. Prerequisites: + 1. List all persons using the `list` command. Multiple persons in the list. + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. The person with NRIC `S9876543N` does not exist in the system. + 1. The visit with date `2021-09-01` and with NRIC `S1234567A` is already created in the system. + 1. The visit with date `2021-09-03` and with NRIC `S9876543N` does not exist in the system. + 1. Test case: `addvisit S1234567A d/2021-09-03 dia/fever sym/cough st/PENDING`
+ Expected: The person's visit is added to the system. The result panel shows the details of the added visit. + 1. Test case: `addvisit S9876543N d/2021-09-03 dia/fever sym/cough st/PENDING`
+ Expected: The person's visit is not added. The result panel shows an error message, indicating that the person does not exist in the system. + 1. Test case: `addvisit S1234567A d/2021-09-01 dia/fever sym/cough st/PENDING`
+ Expected: The person's visit is not added. The result panel shows an error message, indicating that the visit already exists in the system. + 1. Other incorrect addvisit commands to try: `addvisit`, `addvisit 0`, `...` (where the input for NRIC field does not follow format for NRIC)
+ Expected: Similar to previous. The result panel shows an error message, indicating that the command format is invalid. + + +### Checking a person's visit history +1. Checking a person while all persons are being shown. + 1. Prerequisites: + 1. List all persons using the `list` command. Multiple persons in the list. + 1. The person with NRIC `S1234567A` is already created in the system with a `create` command. + + 1. Test case: `check S1234567A`
+ Expected: Details of the checked person's visit history is shown in the result panel. The list panel shows the checked person. + + 1. Test case: `check S9876543N`
+ Expected: No person is checked. Error details shown. + + 1. Other incorrect read commands to try: `check`, `check 0`, `...` (where the input for NRIC field does not follow format for NRIC)
+ Expected: Similar to previous. + 2. +2. Checking a person while only some persons are being shown + 1. Prerequisites: + 2. Show only 1 person's details using the `find n/alex` command. One person is shown in the list. + 3. The person with NRIC `S1234567A` is already created in the system with a `create` command. + 1. Test case: `check S1234567A`
+ Expected: The result panel shows the details of the checked person's visit history. The list panel shows the checked person. + +
+ +## Planned enhancements +**Include FIN as accepted values for field `NRIC`**: The field `NRIC` should accept `F`, `M` and `G` as valid values, as they are valid first characters for foreigners' FIN (foreign identification number). + +**Allow `Name` to take special characters**: The field `Name` should be able to accommodate names with special characters such as dashes, slashes, apostrophes etc. + +**Limit `Country` to a list of valid countries**: The field `Country` should be limited to a list of countries, to prevent invalid entries. + +**Make `Email` case-insensitive**: The field `Email` should be case-insensitive, as emails are not case-sensitive in practice. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 7abd1984218..ab2b6623da7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,40 +2,280 @@ layout: page title: User Guide --- +## Introduction -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. +Streamline Your Care, Empower Your Patients: Welcome to the Healthcare Information Management Revolution! +Are you a doctor who has scrambled for a patient's medical information during a crucial consultation? +Or a clinic receptionist who's gotten frustrated from flipping through stacks of paper notes to recall a patient's phone number? +Those days are over! + +Introducing **ImmuniMate**, our innovative platform designed to revolutionize the way healthcare professionals and staff manage patient information. +It's your secure, centralized hub where you can access all your patients' medical and personal details – allergies, visit history, medical conditions, you name it – just a few clicks away! + +### Who This App is For + +ImmuniMate is designed for **healthcare professionals** (doctors, nurses etc.) and **healthcare staff** (clinic receptionist etc.) longing to leverage the efficiencies of the 21st century, who: + +* have basic experience in using computers +* have a computer with sufficient space (~100 MB) +* are comfortable typing on a regular basis +* have no serious colour vision deficiencies (can differentiate red, yellow and green) + +### Purpose of This Guide + +If you ticked all the points above, great! +This user guide is here as your helping hand, imparting knowledge to unlock the full potential of ImmuniMate. +You can start with learning to navigate this guide effectively by viewing guidelines on how to use this guide. +After that, get started with ImmuniMate by following our step-by-step setup instructions, before really getting involved through short tutorials on its comprehensive set of features. +Get ready to: + +* **Enhance Patient Care**: Deliver faster, more informed consultations with easy access to vital information. +* **Boost Efficiency**: Save precious time by ditching paper records and streamlining your workflow. +* **Revitalise Patients**: Use organised information about your patients to offer them peronalised medical advice. + +Ready to take patient service to the next level? +Time to ditch those stacks of paper and Excel spreadsheets, and embrace the future of healthcare. +Let's dive in and explore how ImmuniMate can transform your practice! + +------------------------------------------- +
+ +## Table of Contents * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- +## How to Use This Guide + +ImmuniMate's user guide is optimised for ease of navigation, so that you can spend less time on the app, and more time on your patients. +The table of contents just before this section breaks down the guide into its constituent sections: + +1. **How to Use This Guide** (this section) + * descriptions on different sections of user guide + * glossary on technical terms +2. **Product Information** + * detailed description of product + * summary of command and fields +3. **Quick Start** + * system requirements + * how to download and get started on the app +4. **Features** + * explains each feature with examples + * common usage mistakes +5. **Frequently Asked Questions** +6. **Known issues** + * descriptions on issues with ImmuniMate that have been spotted, but not fixed + +Navigating a complex document can be time-consuming, and we understand. +That's why we've placed hyperlinks throughout this article (like [this one](#command-summary), to the command summary), so that any information you need is truly at your fingertips. + +
+ +### Information Boxes + +Before engaging with ImmuniMate, there are some things that are so important that they have to catch your eye. +That's why we made these nice blue note boxes to capture your attention, to plug gaps in your understanding before proceeding with ImmuniMate. +
+ +**:information_source: Notes**
+This is what a note box looks like. +
+ +Sometimes, certain instructions might sound very new or contain too many technical terms, which is why we also positioned a few tip snippets below them in green boxes, so that you'll never have to fret about the intricacies of ImmuniMate. +
+ +**:bulb: Tip:**
+This is what a tip snippet looks like. +
+ +Mistakes are inevitable, even in the healthcare industry. +But we should always strive to minimise them, and we're helping you do just that by informing you of common mistakes that have happened countless times. +You can view them in red boxes such as this one. + +
+ +**:x: Common Mistakes**
+This is what a common mistake box looks like. +
+ +
+
+ +ImmuniMate comes with an abundant set of features, each of which we have taken great care to explain in great detail. +Below is the formatting you can expect to see for an explanation of each feature: + +### (what this feature does) : `(command word)` + +(more specific explanation of feature function) + +Format: `(exact usage format with command words and fields)` +* (format detail 1) +* (format detail 2) +* ... + +Examples: +* `(correct use case 1)` + * (consequence) +* `(correct use case 2)` + * (consequence) +* ... + +
+ +**:x: Common Mistakes**
+* `wrong use case 1` (reason) +* `wrong use case 2` (reason) +* ... +
+ +
+ +### Glossary + +Throughout this guide, there might be some terms that you might not be familiar with, and that's fine. +Here's a table of some technical terms you'll see further in the guide: + +| Term | Definition | +|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Java** | A programming language. Various versions can be downloaded from [here](https://www.oracle.com/sg/java/technologies/downloads/). | +| **home folder** | The main folder where all app activity can take place and files can be stored | +| **command terminal** | A text-based interface to your computer.
On Windows, this can be opened by pressing the Windows key, and searching for an app called "Command Prompt".
On MacOS, this can be opened by pressing Command + Space, typing in "terminal", then pressing "Return". | +| `cd` | A Linux (operating system) command used to navigate to different folders in your command terminal. Stands for "change directory".
Linux tutorials can be found [here](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview). | +| **GUI** | Short for "Graphical User Interface". The digital interface in which user interact with graphical components, such as icons and buttons. | +| **CLI** | Short for "Command Line Interface". A software mechanism you use to interact with the application using your keyboard. | + +----------------------------------------------------------------- + +## Product Information + +ImmuniMate is a **desktop application** for healthcare professionals and staff to better store and manage their patients' personal and medical information. +It is optimised for a single user on a single computer, meaning that after downloading a copy on your computer and using it, your copy cannot be accessed through other computers over the Internet. + +ImmuniMate is compatible with **Windows, Linux and MacOS** operating systems, and installation does not require any additional installers. +It has an eye-catching GUI to capture your attention, but despite that, all interactions with ImmuniMate happen through the command line interface (CLI). +This means each feature of ImmuniMate is only accessible through typing a command into the command box in its specified format, and pressing "Enter" to get a response. + +Here is a graphic on components of the GUI and their functions: +
+
+help message +
+
+The list of commands and their formats are specified below: + +### Command summary + +| Action | Format | Examples | +|-------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| **[Create](#creating-a-patient-profile-create)** | `create a/
[e/Email] [c/Country_of_Nationality] [doa/Date_of_Admission] [bt/Blood type] [al/Allergies] [con/Condition] [sym/Symptom] [d/diagnosis]` | `create ic/S1234567A n/John Doe hp/98765432 a/311, Clementi Ave 2, #02-25 dob/1990-01-01 s/M st/PENDING` | +| **[Read](#read-specific-patients-read)** | `read ` | `read S1234567A` | +| **[Update](#updating-a-patient-profile-update)** | `update /` | `update S1234567A hp/91234567 e/jd123@example.com` | +| **[Find](#finding-patients-by-name-find)** | `find n/ `
`find a/, , , ...`
`find con/, , , ...` | `find n/Alex Bryan Charlie`
`find a/Serangoon, Geylang`
`find con/Covid, Ebola` | +| **[Delete Patient](#deleting-a-patient-delete)** | `delete ` | `delete S1234567A` | +| **[Delete Information](#deleting-information-of-a-patient-deleteinfo)** | `deleteinfo ` | `deleteinfo S1234567A e/` | +| **[Add Visit](#add-patient-visit-to-history-addvisit)** | `addvisit ic/ dov/ sym/ d/ st/` | `addvisit ic/S1234567A dov/2024-01-01 sym/Cough d/Covid st/UNWELL` | +| **[Check](#check-patient-history-check)** | `check ` | `check S1234567A` | +| **[Find Cluster](#cluster-finding-cluster)** | `cluster a/ d/` | `cluster 3 a/Serangoon d/dengue` | +| **[Clear](#clearing-all-entries-clear)** | `clear` | - | +| **[List](#listing-all-patients-list)** | `list` | - | +| **[Help](#viewing-help-help)** | `help` | - | +| **[Exit](#exiting-the-program-exit)** | `exit` | - | + +
+
+The list of fields and their formats are specified below: + +### Field summary + +| Field | Prefix | Format | Mandatory | +|----------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| **Name** | `n/` | The name of the patient. Case-insensitive alphanumeric characters and spaces, capped at 2,000,000,000 characters. | Yes | +| **NRIC** | `ic/` | First character can be either S or T, followed by 7 digits, and the last character is an alphabetical letter, in accordance with Singapore NRIC formats. NRIC is case-insensitive. | Yes | +| **Phone Number** | `hp/` | 8 digits. | Yes | +| **Address** | `a/` | Any text. Blank or empty text is not accepted. | Yes | +| **Date of birth** | `dob/` | `yyyy-MM-dd` format. | Yes | +| **Sex** | `s/` | `M` or `F`, case-sensitive. | Yes | +| **Status** | `st/` | `PENDING`, `UNWELL`or `HEALTHY`, case-insensitive. | Yes | +| **Email** | `e/` | Any valid email address of the form `local-part@domain`, case-sensitive. | No | +| **Country of nationality** | `c/` | Any text. Blank or empty text is not accepted. | No | +| **Date of admission** | `doa/` | `yyyy-MM-dd` format. | No | +| **Blood type** | `bt/` | Any of `A+`, `A-`, `B+`, `B-`, `AB+`, `AB-`, `O+`, `O-` | No | +| **Allergies** | `al/` | Any text. Blank or empty text is not accepted. | No | +| **Condition** | `con/` | Any text. Blank or empty text is not accepted. | No | +| **Symptom** | `sym/` | Any text. Blank or empty text is not accepted. | No | +| **Diagnosis** | `d/` | Any text. Blank or empty text is not accepted. | No | +| **Date of visit** | `dov/` | `yyyy-MM-dd` format. | No | + +### Error Messages + +Sometimes, **you might type in commands in the wrong format**, or fields that don't make sense, and that's fine. +When that happens, the erroneous command you typed will light up in red, while more details on the nature of the error will be shown in the feedback box, like in the picture below. + +![Error Message](images/ErrorMessage.png) + +Not to worry, you can just edit that command, or delete it and type in a correct one.
+ +
+ +**:bulb: Tip:**
+Find retyping commands a hassle? Use your 'Up' and 'Down' arrow keys to access your past commands saved in your [Command History](#command-history)! +
+ +---------------------------------------------------------------- ## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. Ensure you have Java `11` or above installed in your computer.
+ +
+ + **:bulb: Tip:**
+ Don't worry if you don't have Java 11 installed yet! + The Java Development Kit (kind of like an installer) can be downloaded from [here](https://www.oracle.com/sg/java/technologies/downloads/#java11) (screenshot below). + Take great care in downloading the one which suits your operating system (Linux, Windows, MacOS etc). +
+ + ![Java website](images/JavaWebsite.png) + +2. Download the latest `immuniMate.jar` from [our website](https://github.com/AY2324S2-CS2103T-T08-1/tp/releases). + + help message -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +3. Copy the file to the folder you want to use as the _home folder_ for your ImmuniMate. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +4. Open a command terminal, and `cd` into the folder you put the jar file in.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +
+ + **:bulb: Tip:**
+ `cd` is a Linux command. New to Linux? You can learn the basics fast from [here](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview). +
-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.
+5. Type `java -jar immuniMate.jar` and press "Enter" to run the application.
+ A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data. The colored circle on the right of each patient's name is the status indicator. For more information about the status indicator, see [create](#creating-a-patient-profile-create).
+ + ![Ui](images/GUI.png) + +6. Type the command in the command box and press "Enter" to execute it. e.g. typing **`help`** and pressing "Enter" will open the help window.
Some example commands you can try: * `list` : Lists all contacts. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * `create ic/S0123456A n/John Doe hp/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 dob/1990-01-01 s/M st/PENDING` : Adds a patient named `John Doe` to ImmuniMate. - * `delete 3` : Deletes the 3rd contact shown in the current list. + * `delete S0123456A` : Deletes all information of the patient with corresponding NRIC. - * `clear` : Deletes all contacts. + * `clear` : Deletes all patients. * `exit` : Exits the app. -1. Refer to the [Features](#features) below for details of each command. +
+ + **:bulb: Tip:**
+ Refer to the [Features](#features) section below for details of each command. + +
-------------------------------------------------------------------------------------------------------------------- @@ -43,19 +283,19 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
-**:information_source: Notes about the command format:**
+**:information_source: Notes**
-* 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`. +* Words in `UPPERCASE` are the parameters to be supplied by the user.
+ e.g. in `update /CONTENT`, `CONTENT` is a parameter which can be used as `update S0123456A hp/87654321`. -* 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 in angle brackets are mandatory.
+ e.g. `` must be given. -* 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 in square brackets are optional.
+ e.g. ` [e/EMAIL]` can be used as `ic/S0123456A e/jd123@example.com` or as `ic/S0123456A`. * 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. + e.g. if the command specifies `n/ hp/`, `hp/ n/` is also acceptable. * 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`. @@ -63,136 +303,416 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-### Viewing help : `help` +
+ +### Viewing help: `help` -Shows a message explaning how to access the help page. +**Shows a message explaining how to access the help page.** -![help message](images/helpMessage.png) +help message Format: `help` +
-### Adding a person: `add` +### Listing all patients: `list` -Adds a person to the address book. +**Shows all patients in ImmuniMate.** -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: `list` + +
+ +### Creating a patient profile: `create` + +**Creates a patient profile in ImmuniMate.** + +Format: `create ic/ n/ hp/ a/
dob/ s/ st/ [e/Email] [c/Country_of_Nationality] [doa/Date_of_Admission] [bt/Blood type] [al/Allergies] [con/Condition] [sym/Symptom] [d/diagnosis]` + +* All mandatory fields must be provided. Refer to the [Field Summary](#field-summary) for a list of mandatory and optional fields, and their formats. +* The unique identifier for each patient is the NRIC. The new NRIC must not already exist in the system. +* The status of the patient is indicated by a colored circle on the right of the patient's name. The color of the circle corresponds to the status of the patient. The status can be `PENDING` (yellow), `UNWELL`(red) or `HEALTHY`(green). + +Examples: +* `create ic/S1234567A n/John Doe hp/98765432 a/311, Clementi Ave 2, #02-25 dob/1990-01-01 s/M st/PENDING` +* `create ic/S0123456A hp/87654321 a/311, Clementi Ave 2, #02-25 dob/1990-01-01 s/F st/PENDING e/janed@example.com bt/A+ n/Jane Doe` + +![Create Command Result](images/CreateCommandResult.png) + +
-
:bulb: **Tip:** -A person can have any number of tags (including 0) +**:x: Common Mistakes**
+* `create n/John Doe hp/98765432 a/311, Clementi Ave 2, #02-25 dob/1990-01-01 s/M st/PENDING` (missing NRIC) +* `create ic/S1234567A n/John Doe hp/98765432 a/311, Clementi Ave 2, #02-25 dob/1990-2-30 s/M st/PENDING` (Wrong date format)
+
+ +### Read specific patients: `read` + +**Shows all profile details of patient with corresponding NRIC.** + +Format: `read ` +* The NRIC must follow the correct format specified in [Field Summary](#field-summary). + 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` +* `read t0234567c` -### Listing all persons : `list` +![Read Command Result](images/ReadCommandResult.png) -Shows a list of all persons in the address book. +
-Format: `list` +**:x: Common Mistakes**
+* `read S12345678` (wrong NRIC format) +
+ +
-### Editing a person : `edit` +### Updating a patient profile: `update` -Edits an existing person in the address book. +**Updates information of a patient with an existing profile in ImmuniMate.** -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `update /CONTENT` -* 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. +* Updates information of the patient with corresponding NRIC. +* At least one of the fields must be provided. +* NRIC cannot be updated, all other values can be updated. * 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. +* Refer to the [Field Summary](#field-summary) for a list of fields and their formats.
+ +
+ +**:bulb: Tip**
+Just finished a consultation with a patient? +You can update related fields together, like changing the patient's diagnosis to "coronavirus" while changing the status to "UNWELL". +
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. +* `update S1234567A hp/91234567 e/jd123@example.com` + * Updates the phone number and email address of the corresponding patient to be `91234567` and `jd123@example.com` respectively. +* `update S0123456A a/123 Serangoon Road` + * Updates the address of the corresponding patient to be `123 Serangoon Road`. -### Locating persons by name: `find` +result for 'find alex david' -Finds persons whose names contain any of the given keywords. +
-Format: `find KEYWORD [MORE_KEYWORDS]` +**:x: Common Mistakes**
+* `update S1234567A ic/T1234567A` (NRIC cannot be updated) +* `update S1234567A` (no field specified) +
+ +
+ +### Finding patients by name: `find` + +**Finds patients whose names contain any of the given keywords.** + +Format: `find n/ [NAME] [NAME] ...` -* 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` +* 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 field in each patient profile is searched. +* At least one name must be provided. +* Only full words will be matched e.g. `Han` will not match `Hans`. +* Patients matching at least one keyword will be returned (i.e. `OR` search). + e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`. +* Names can be separated with any number of spaces. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `find n/John` + * Returns `John Dolly` and `John Doe`. +* `find n/alex david` + * Returns `Alex Yeoh`, `David Li`.
-### Deleting a person : `delete` +result for 'find alex david' -Deletes the specified person from the address book. +

-Format: `delete INDEX` +### Finding patients by address: `find` -* 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, …​ +**Finds patients whose addresses contain any of the given keywords.** + +Format: `find a/, [LOCATION], [LOCATION], ...` + +* The search is case-insensitive. e.g. `serangoon` will match `Serangoon`. +* The order of the keywords does matter. e.g. `Clementi Ave` will not match `Ave Clementi`. +* Only the address field in each patient profile is searched. +* At least one location must be provided. +* Partial words will be matched e.g. `Clem` will match `Clementi`. +* Patients matching at least one keyword will be returned (i.e. `OR` search). + e.g. `Clementi, Serangoon` will return patients with address containing `Serangoon` or `Clementi`. +* Locations must be separated by commas, and whitespaces before and after each location will be ignored. Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +* `find a/Geylang` + * Returns `Alex Yeoh`. +* `find a/geylang, serangoon, choa chu kang` + * Returns `Alex Yeoh`, `Bernice Yu`, `David Li`. +* `find a/Serangoon` + * Returns `Bernice Yu`, `David Li`.
+ +result for 'find serangoon' + +

+ +### Finding patients by condition: `find` -### Clearing all entries : `clear` +**Finds patients whose conditions contain any of the given keywords.** -Clears all entries from the address book. +Format: `find con/, [CONDITION], [CONDITION], ...` + +* The search is case-insensitive. e.g. `diabetes` will match `Diabetes`. +* The order of the keywords does matter. e.g. `Stomach Flu` will not match `Flu Stomach`. +* Only the condition is searched. +* At least one condition must be provided. +* Partial words will be matched e.g. `diab` will match `Diabetes`. +* Patients matching at least one keyword will be returned (i.e. `OR` search). + e.g. `Diabetes, Myopia` will return patients with address containing `Diabetes` or `Myopia`. +* Conditions must be separated by commas, and whitespaces before and after each condition will be ignored. + +Examples: +* `find con/diabetes, high blood pressure` + +result for 'find serangoon' + +
+ +**:x: Common Mistakes**
+* `find ic/S1234567X` (only condition, name and address can be searched with find)
+
+ +
+ +**:bulb: Tip:**
+If you would like to find a person with NRIC, use the `read` command instead. +
+ +
+ +### Deleting a patient: `delete` + +**Deletes the specified patient from ImmuniMate.** + +Format: `delete ` + +* Deletes the profile of the patient with the corresponding NRIC. +* The NRIC must follow the correct format as specified in [Field Summary](#field-summary). + +Examples: +* `delete S1234567A` + * deletes patient uniquely identified by NRIC S1234567A. + +
+ +**:x: Common Mistakes**
+* `delete S12345678` (NRIC must be in the correct format) +* `delete` (NRIC must be provided) +
+ +
+ +### Deleting information of a patient: `deleteinfo` + +**Deletes specified optional information from the specified person from ImmuniMate.** + +Format: `deleteinfo [Field] [Field] ...` + +* Deletes specified information of the patient with corresponding NRIC. +* The NRIC must follow the correct format as specified in [Field Summary](#field-summary). +* Only fields specified as optional in the [Field Summary](#field-summary) can be deleted. + +Examples: +* `deleteinfo S1234567A e/` + * Deletes the email of patient uniquely identified by NRIC S1234567A. +* `deleteinfo S0123456A e/ bt/ c/` + * Deletes the email, blood type and country of nationality of patient uniquely identified by NRIC S0123456A. + +
+ +**:x: Common Mistakes**
+* `deleteinfo S1234567A abc/` (a valid optional field from the [Field Summary](#field-summary) must be provided) +* `deleteinfo S1234567A` (a field must be provided) +* `deleteinfo S1234567A ic/` (mandatory fields cannot be deleted)
+
+ +
+ +**:bulb: Tip:**
+If you would like to change mandatory fields, you can use the `update` command instead. +
+ +
+ +### Add patient visit to history: `addvisit` + +**Adds visit to patient history.** + +Format: `addvisit ic/ dov/ sym/ d/ st/` + +* NRIC must be that of a patient already in ImmuniMate. + +Examples: +* `addvisit ic/S1234567A dov/2024-01-01 sym/Cough d/Covid st/UNWELL` + * Adds a visit to history of patient uniquely identified by NRIC S1234567A. During this visit on 2024-01-01, the patient was having a cough and was diagnosed to be unwell with Covid. +* `addvisit ic/S0123456A dov/2024-02-02 sym/Fever,Rashes d/possible dengue st/PENDING` + * Adds a visit to history of patient uniquely identified by NRIC S0123456A. During this visit on 2024-02-02, the patient had a fever and rashes. The doctor suspects the patient has dengue, but is unable to come to a conclusion, hence the `PENDING` status. + +![Add Visit Result](images/AddVisitCommandResult.png) + +
+ +**:x: Common Mistakes**
+* `addvisit ic/S7654321X dov/2024-01-01 sym/Cough d/Covid st/` (NRIC must belong to a person existing in the system) +* `addvisit ic/S1234567A a/#101 Hougang Ave` (fields other than date of visit, symptoms, diagnosis and status cannot be added) +
+ +
+ +### Check patient history: `check` + +**Checks all visits in patient history.** + +Format: `check ` +* NRIC must be that of a patient already in ImmuniMate. + +Example: +* `check T0234567C` + * Displays all visits in history of patient uniquely identified by NRIC S1234567A. + +![Check Command Result](images/CheckCommandResult.png) + +
+ +**:x: Common Mistakes**
+* `check S12345678` (NRIC must be in the correct format, and must exist in the system) +
+ +
+ +### Cluster finding: `cluster` + +**Shows whether or not the number of people _unwell_ with the illness given (diagnosis in profile) in the location given is at least the integer given, and lists the people there with the illness.** + +Format: `cluster a/ d/` + +* The search is case-insensitive. e.g. `serangoon` will match `Serangoon`. +* Only one location and diagnosis is searched. +* Location and diagnosis cannot be empty. +* Cluster size must be between 1 and 2,000,000,000. +* Partial words will be matched e.g. `Clem` will match `Clementi`, `deng` will match `dengue`. + +Example: +* `cluster 3 a/Serangoon d/dengue` + * Shows if there are at least 3 people unwell with dengue in locations with the substring "Serangoon". + +![Cluster Command Result](images/ClusterCommandResult.png) + +
+ +**:x: Common Mistakes**
+* `cluster 3 a/S d/dengue` (address should be a meaningful word or phrase indicative of a location in Singapore) +* `cluster 3 a/Serangoon` (diagnosis must be provided) +* `cluster 3 d/dengue` (diagnosis must be provided) +* `cluster 0 a/Serangoon d/dengue` (a positive cluster size must be provided) +* `cluster 30.5 a/Serangoon d/dengue` (an integer cluster sizet must be provided) +
+ +
+ +### Clearing all entries: `clear` + +**Clears all profiles from ImmuniMate.** Format: `clear` -### Exiting the program : `exit` +
-Exits the program. +### Exiting the program: `exit` + +**Exits the program.** Format: `exit` +
+ +### Command History +ImmuniMate allows you to navigate through your previous commands so you can easily reuse them without having to +retype them entirely. To navigate through the Command History, use the Up Arrow Key to view a previous command, and use the Down +Arrow Key to view the next command. The Up and Down Arrow Keys can be found on the Arrow Keys. + +![Keyboard Arrow Keys](images/KeyboardArrowKeys.png) + +
+ +**:information_source: Notes**
+* The Command History only saves **valid commands**, it does not save commands that were unsuccessful. +* The Command History is **temporary** and **will not be stored in between sessions**. When you close an instance of ImmuniMate, your Command History is cleared. +
+ +
+ ### 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. +ImmuniMate data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually. + +
### Editing the data file -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +ImmuniMate data are saved automatically as a JSON file `[JAR file location]/data/immunimate.json`. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. +If your changes to the data file makes its format invalid, ImmuniMate will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause ImmuniMate to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-### Archiving data files `[coming in v2.0]` +
+ +### Update patient's visit in history `[coming in v2.0]` +- Allows updating of patient's visit in history. + +### Automated Infection cluster tracking `[coming in v2.0]` +- Automatically tracks clusters and stores them in storage. -_Details coming soon ..._ -------------------------------------------------------------------------------------------------------------------- ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous ImmuniMate home folder. --------------------------------------------------------------------------------------------------------------------- +**Q**: I entered a command and don't want to type it again, how can I go back to it?
+**A**: Use the up and down arrow keys on the keyboard to navigate your Command History. More information is in the Command History section above. -## Known issues +**Q**: I added a new Patient Visit to my patient, but it doesn't seem to update the Patient information. Is something wrong?
+**A**: No, this is intended behaviour as we want to afford more flexibility to GPs. Automatically updating the patient field based +on recent visits might result in accidentally overriding intended data. -1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. +**Q**: My name has dashes, slashes, commas, or apostrophes, I can't input my name in the create command.
+**A**: This is intended behaviour. Our name field aims to adhere to ICA guidelines. +If your name has any special characters, kindly remove them for compliance. For example, "Lee Chi-Geng, Bryan" can be changed into "Lee Chi Geng Bryan". + +**Q**: Help, I can't add multiple patient visits a day!
+**A**: This is intended behaviour. Patients are assumed to only have a single visit a day. + +**Q**: Why can I assign multiple patient profiles with the same phone number?
+**A**: This is intended behaviour. We understand there might be some patients (elderly, young children, disabled etc) who might be dependent on their family members or guardians for their day-to-day tasks. These family members and guardians might themselves be patients of the same clinic, hence the flexibility in recording phone numbers. +**Q**: Why is it when I update a patient's diagnosis, his/her status is not automatically updated to "UNWELL"?
+**A**: This is intended behaviour. We wish to leave it to your expertise to determine when a patient has truly contracted a disease, as there are some ambiguous cases which might not necessitate an "UNWELL" status, such as asymptomatic coronavirus cases. + +**Q**: Why is it that when I update a person's field to the exact same content as the current one, no error is shown? Similarly, why is that when I delete a field that originally was not filled, no error message is shown?
+**A**: This is intended behaviour. This operation does not cause error in the system as the content is the same, and we do not want to interrupt your workflow with unnecessary error messages. -------------------------------------------------------------------------------------------------------------------- -## 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` +## Known issues + +1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. +2. **When using the `cluster` command**, search is purely based on text, which means inputting "ave" will find all unwell patients whose addresses contain "ave", despite the addresses not necessarily being close to one another. We suggest users to input text indicative of location, such as "Hougang". +3. **The `country` field** does not limit the input to alphabetical characters and is case-insensitive, which may lead to incorrect data entry. +4. **The `email` field** is case-sensitive, but in practical usage, email is case-insensitive. +5. **The `NRIC` field** cannot yet take NRIC numbers starting with F, G or M, which might cause inconvenience to a small segment of the Singapore population. +6. **The ImmuniMate icon** cannot be displayed on Windows systems, instead showing up as a brown square with a person icon. This is simply a cosmetic issue, so it should not pose other technical issues to Windows users. +7. **The `DateOfBirth`, `DateOfVisit` and `DateOfAdmission` fields** do allow future dates to be added, which might cause inconsistent entry if input is wrong. diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..c5d993669f8 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "ImmuniMate" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2324S2-CS2103T-T08-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..c0dc0641e3f 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: "ImmuniMate"; font-size: 32px; } } diff --git a/docs/diagrams/AddVisitCommandLogic.puml b/docs/diagrams/AddVisitCommandLogic.puml new file mode 100644 index 00000000000..97d309324cb --- /dev/null +++ b/docs/diagrams/AddVisitCommandLogic.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":AddVisitCommandParser" as AddVisitCommandParser LOGIC_COLOR +participant "c:AddVisitCommand" as AddVisitCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +participant "v:Visit" as Visit MODEL_COLOR +end box + +[-> LogicManager : execute("addvisit ...") +activate LogicManager +LogicManager -> ImmuniMateParser : parseCommand("addvisit ...") +activate ImmuniMateParser + +create AddVisitCommandParser +ImmuniMateParser -> AddVisitCommandParser +activate AddVisitCommandParser +AddVisitCommandParser --> ImmuniMateParser +deactivate AddVisitCommandParser +ImmuniMateParser -> AddVisitCommandParser : parse("...") +activate AddVisitCommandParser + +create Visit +AddVisitCommandParser -> Visit +activate Visit +Visit --> AddVisitCommandParser +deactivate Visit + +create AddVisitCommand +AddVisitCommandParser -> AddVisitCommand +activate AddVisitCommand +AddVisitCommand --> AddVisitCommandParser +deactivate AddVisitCommand + +AddVisitCommandParser --> ImmuniMateParser : c +deactivate AddVisitCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddVisitCommandParser -[hidden]-> ImmuniMateParser +destroy AddVisitCommandParser +ImmuniMateParser --> LogicManager : c +deactivate ImmuniMateParser + +LogicManager -> AddVisitCommand : execute() +activate AddVisitCommand +AddVisitCommand -> Visit : getNric() +activate Visit +Visit --> AddVisitCommand : nric +deactivate Visit +AddVisitCommand -> Model : hasPerson(...) +activate Model +Model --> AddVisitCommand : true +deactivate Model +AddVisitCommand -> Model : hasVisit(...) +activate Model +Model --> AddVisitCommand : false +deactivate Model +AddVisitCommand -> Model : addVisit(v) +activate Model +Model --> AddVisitCommand : add visit in the model +deactivate Model + +create CommandResult +AddVisitCommand -> CommandResult +activate CommandResult +CommandResult --> AddVisitCommand +deactivate +AddVisitCommand --> LogicManager : r +deactivate AddVisitCommand + +[<--LogicManager : r +deactivate LogicManager +@enduml diff --git a/docs/diagrams/AddVisitCommandModel.puml b/docs/diagrams/AddVisitCommandModel.puml new file mode 100644 index 00000000000..07fbb2948d4 --- /dev/null +++ b/docs/diagrams/AddVisitCommandModel.puml @@ -0,0 +1,119 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as Logic LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniqueVisitList" as UniqueVisitList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +participant "v:Visit" as Visit MODEL_COLOR +end box + +[-> Logic : execute() +activate Logic + +create p +Logic -> p +activate p +p --> Logic +deactivate p + +create Visit +Logic -> Visit +activate Visit +Visit --> Logic +deactivate Visit + +Logic -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate -> UniqueVisitList : contains(p) +activate UniqueVisitList + +UniqueVisitList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson() +activate p + +p --> internalList +deactivate p + +internalList --> UniqueVisitList +deactivate internalList + +UniqueVisitList --> ImmuniMate +deactivate UniqueVisitList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> Logic +deactivate ModelManager + +Logic -> ModelManager : hasVisit(v) +activate ModelManager + +ModelManager -> ImmuniMate: hasVisit(v) +activate ImmuniMate + +ImmuniMate -> UniqueVisitList : contains(v) +activate UniqueVisitList + +UniqueVisitList -> internalList : stream().anyMatch(v::isSameVisit) +activate internalList + +internalList -> Visit : isSameVisit() +activate Visit + +Visit --> internalList +deactivate Visit + +internalList --> UniqueVisitList +deactivate internalList + +UniqueVisitList --> ImmuniMate +deactivate UniqueVisitList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> Logic +deactivate ModelManager + +Logic -> ModelManager : addVisit(v) +activate ModelManager + +ModelManager -> ImmuniMate: addVisit(v) +activate ImmuniMate + +ImmuniMate -> UniqueVisitList : add(v) +activate UniqueVisitList + +UniqueVisitList -> internalList : add(v) +activate internalList + +internalList --> UniqueVisitList +deactivate internalList + +UniqueVisitList --> ImmuniMate +deactivate UniqueVisitList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> Logic +deactivate ModelManager + +[<--Logic +deactivate Logic + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..a1a42a2832f 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,10 +8,10 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete S1234567X" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete S1234567X") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) @@ -20,7 +20,7 @@ activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveAddressBook(immuniMate) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/CheckCommandModelDiagram.puml b/docs/diagrams/CheckCommandModelDiagram.puml new file mode 100644 index 00000000000..04e620bf78c --- /dev/null +++ b/docs/diagrams/CheckCommandModelDiagram.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as CheckCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> CheckCommand : execute() +activate CheckCommand + +CheckCommand -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate --> UniquePersonList : contains(p) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson(p) +activate p + +p --> internalList +deactivate p + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> CheckCommand +deactivate ModelManager + +CheckCommand -> ModelManager : updateFilteredPersonList(predicate) +activate ModelManager + +ModelManager --> CheckCommand +deactivate ModelManager + +CheckCommand -> ModelManager : updateFilteredVisitList(predicate) +activate ModelManager + +ModelManager --> CheckCommand +deactivate ModelManager + +[<--CheckCommand +deactivate CheckCommand + +@enduml diff --git a/docs/diagrams/CheckCommandSequenceDiagram.puml b/docs/diagrams/CheckCommandSequenceDiagram.puml new file mode 100644 index 00000000000..58607180c94 --- /dev/null +++ b/docs/diagrams/CheckCommandSequenceDiagram.puml @@ -0,0 +1,94 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":CheckCommandParser" as CheckCommandParser LOGIC_COLOR +participant "c:CheckCommand" as CheckCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("check T0123456A") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("check T0123456A") +activate AddressBookParser + +create CheckCommandParser +AddressBookParser -> CheckCommandParser +activate CheckCommandParser + +CheckCommandParser --> AddressBookParser +deactivate CheckCommandParser + +AddressBookParser -> CheckCommandParser : parse("T0123456A") +activate CheckCommandParser + +create CheckCommand +CheckCommandParser -> CheckCommand +activate CheckCommand + +CheckCommand --> CheckCommandParser : +deactivate CheckCommand + +CheckCommandParser --> AddressBookParser : c +deactivate CheckCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CheckCommandParser -[hidden]-> AddressBookParser +destroy CheckCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CheckCommand : execute(m) +activate CheckCommand + +CheckCommand -> Model : hasPerson(...) +activate Model + +Model --> CheckCommand : false +deactivate Model + +CheckCommand -> Model : updateFilteredPersonList(...) +activate Model + +Model --> CheckCommand : updateFilteredPersonList() successful +deactivate Model + +CheckCommand -> Model : getFilteredPersonList() +activate Model + +Model --> CheckCommand : Filtered person list +deactivate Model + +CheckCommand -> Model : updateFilteredVisitList(...) +activate Model + +Model --> CheckCommand : updateFilteredVisitList() successful +deactivate Model + +CheckCommand -> Model : getFilteredVisitList() +activate Model + +Model --> CheckCommand : Filtered visit list +deactivate Model + +create CommandResult +CheckCommand -> CommandResult +activate CommandResult + +CommandResult --> CheckCommand +deactivate CommandResult + +CheckCommand --> LogicManager : r +deactivate CheckCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ClusterLogicModelDiagram.puml b/docs/diagrams/ClusterLogicModelDiagram.puml new file mode 100644 index 00000000000..9f20bfbcf13 --- /dev/null +++ b/docs/diagrams/ClusterLogicModelDiagram.puml @@ -0,0 +1,63 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":ClusterCommandParser" as ClusterCommandParser LOGIC_COLOR +participant "c:ClusterCommand" as ClusterCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("cluster 50 ...") +activate LogicManager +LogicManager -> ImmuniMateParser : parseCommand("cluster 50 ...") +activate ImmuniMateParser + +create ClusterCommandParser +ImmuniMateParser -> ClusterCommandParser +activate ClusterCommandParser +ClusterCommandParser --> ImmuniMateParser +deactivate ClusterCommandParser +ImmuniMateParser -> ClusterCommandParser : parse("50 ...") +activate ClusterCommandParser + +create ClusterCommand +ClusterCommandParser -> ClusterCommand +activate ClusterCommand +ClusterCommand --> ClusterCommandParser +deactivate ClusterCommand +ClusterCommandParser --> ImmuniMateParser : c +deactivate ClusterCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ClusterCommandParser -[hidden]-> ImmuniMateParser +destroy ClusterCommandParser +ImmuniMateParser --> LogicManager : c +deactivate ImmuniMateParser +LogicManager -> ClusterCommand : execute(m) +activate ClusterCommand +ClusterCommand -> Model : updateFilteredPersonList(...) +activate Model +Model --> ClusterCommand +deactivate Model +ClusterCommand -> Model : getFilteredPersonList(...) +activate Model +Model --> ClusterCommand +deactivate Model + +create CommandResult +ClusterCommand -> CommandResult +activate CommandResult +CommandResult --> ClusterCommand +deactivate +ClusterCommand --> LogicManager : r +deactivate ClusterCommand + +[<--LogicManager : r +deactivate LogicManager +@enduml diff --git a/docs/diagrams/CreateCommandLogic.puml b/docs/diagrams/CreateCommandLogic.puml new file mode 100644 index 00000000000..86638d4f50e --- /dev/null +++ b/docs/diagrams/CreateCommandLogic.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":CreateCommandParser" as CreateCommandParser LOGIC_COLOR +participant "c:CreateCommand" as CreateCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +participant "p:Person" as Person MODEL_COLOR +end box + +[-> LogicManager : execute("create ...") +activate LogicManager +LogicManager -> ImmuniMateParser : parseCommand("create ...") +activate ImmuniMateParser + +create CreateCommandParser +ImmuniMateParser -> CreateCommandParser +activate CreateCommandParser +CreateCommandParser --> ImmuniMateParser +deactivate CreateCommandParser +ImmuniMateParser -> CreateCommandParser : parse("...") +activate CreateCommandParser + +create Person +CreateCommandParser -> Person +activate Person +Person --> CreateCommandParser +deactivate Person + +create CreateCommand +CreateCommandParser -> CreateCommand +activate CreateCommand +CreateCommand --> CreateCommandParser +deactivate CreateCommand +CreateCommandParser --> ImmuniMateParser : c +deactivate CreateCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CreateCommandParser -[hidden]-> ImmuniMateParser +destroy CreateCommandParser +ImmuniMateParser --> LogicManager : c +deactivate ImmuniMateParser +LogicManager -> CreateCommand : execute() +activate CreateCommand +CreateCommand -> Model : hasPerson(p) +activate Model +Model --> CreateCommand : false +deactivate Model +CreateCommand -> Model : addPerson(p) +activate Model +Model --> CreateCommand : add person in the model +deactivate Model + +create CommandResult +CreateCommand -> CommandResult +activate CommandResult +CommandResult --> CreateCommand +deactivate +CreateCommand --> LogicManager : r +deactivate CreateCommand + +[<--LogicManager : r +deactivate LogicManager +@enduml diff --git a/docs/diagrams/CreateCommandModel.puml b/docs/diagrams/CreateCommandModel.puml new file mode 100644 index 00000000000..edd81b9bde2 --- /dev/null +++ b/docs/diagrams/CreateCommandModel.puml @@ -0,0 +1,82 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as CreateCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> CreateCommand : execute() +activate CreateCommand + +create p +CreateCommand -> p +activate p +p --> CreateCommand +deactivate p + +CreateCommand -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate -> UniquePersonList : contains(p) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson() +activate p + +p --> internalList +deactivate p + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> CreateCommand +deactivate ModelManager + +CreateCommand -> ModelManager : addPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: addPerson(p) +activate ImmuniMate + +ImmuniMate -> UniquePersonList : add(p) +activate UniquePersonList + +UniquePersonList -> internalList : add(p) +activate internalList + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> CreateCommand +deactivate ModelManager + +[<--CreateCommand +deactivate CreateCommand + +@enduml diff --git a/docs/diagrams/DeleteInfoModelDiagram.puml b/docs/diagrams/DeleteInfoModelDiagram.puml new file mode 100644 index 00000000000..8e5b84ceff1 --- /dev/null +++ b/docs/diagrams/DeleteInfoModelDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as CreateCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p1:Person" as p1 MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> CreateCommand : execute() +activate CreateCommand + +CreateCommand -> ModelManager : getFilteredPersonsList() +activate ModelManager + +ModelManager --> CreateCommand : persons +deactivate ModelManager + +CreateCommand -> ModelManager : hasPerson(...) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p1) +activate ImmuniMate + +ImmuniMate -> UniquePersonList : contains(p1) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p1::isSamePerson) +activate internalList + +internalList -> p1 : isSamePerson(p1) +activate p1 + +p1 --> internalList : true +deactivate p1 + +internalList --> UniquePersonList : true +deactivate internalList + +UniquePersonList --> ImmuniMate : true +deactivate UniquePersonList + +ImmuniMate --> ModelManager : true +deactivate ImmuniMate + +ModelManager --> CreateCommand : true +deactivate ModelManager + +CreateCommand -> ModelManager : getFilteredPersonList() +activate ModelManager +ModelManager --> CreateCommand : internalList +deactivate ModelManager + +CreateCommand -> internalList : filtered().get(0) +activate internalList +internalList --> CreateCommand : p +deactivate internalList + +CreateCommand -> p : setEmail(null) +activate p +p --> CreateCommand : set email to null +deactivate p + +[<--CreateCommand +deactivate CreateCommand + +@enduml diff --git a/docs/diagrams/DeleteInfoSequenceDiagram.puml b/docs/diagrams/DeleteInfoSequenceDiagram.puml new file mode 100644 index 00000000000..d2dbd322ed0 --- /dev/null +++ b/docs/diagrams/DeleteInfoSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":DeleteInfoCommandParser" as DeleteInfoCommandParser LOGIC_COLOR +participant "d:DeleteInfoCommand" as DeleteInfoCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +participant "p:Person" as Person MODEL_COLOR +end box + +[-> LogicManager : execute("deleteinfo S1234567X e/") +activate LogicManager + +LogicManager -> ImmuniMateParser : parseCommand("deleteinfo S1234567X e/") +activate ImmuniMateParser + +create DeleteInfoCommandParser +ImmuniMateParser -> DeleteInfoCommandParser +activate DeleteInfoCommandParser + +DeleteInfoCommandParser --> ImmuniMateParser +deactivate DeleteInfoCommandParser + +ImmuniMateParser -> DeleteInfoCommandParser : parse("S1234567X e/") +activate DeleteInfoCommandParser + +create DeleteInfoCommand +DeleteInfoCommandParser -> DeleteInfoCommand +activate DeleteInfoCommand + +DeleteInfoCommand --> DeleteInfoCommandParser : +deactivate DeleteInfoCommand + +DeleteInfoCommandParser --> ImmuniMateParser : d +deactivate DeleteInfoCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteInfoCommandParser -[hidden]-> ImmuniMateParser +destroy DeleteInfoCommandParser + +ImmuniMateParser --> LogicManager : d +deactivate ImmuniMateParser + +LogicManager -> DeleteInfoCommand : execute(m) +activate DeleteInfoCommand + +DeleteInfoCommand -> Model : getFilteredPersonsList() +activate Model + +Model --> DeleteInfoCommand : persons +deactivate Model + +DeleteInfoCommand -> Model : hasPerson(...) +activate Model + +Model --> DeleteInfoCommand : true +deactivate Model + +DeleteInfoCommand -> Model : getFilteredPersonList().filtered().get(0) +activate Model +Model --> DeleteInfoCommand : p +deactivate Model + +DeleteInfoCommand -> Person : setEmail(null) +activate Person +Person --> DeleteInfoCommand : set email of specified person to null +deactivate Person + +create CommandResult +DeleteInfoCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteInfoCommand +deactivate CommandResult + +DeleteInfoCommand --> LogicManager : r +deactivate DeleteInfoCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteModelDiagram.puml b/docs/diagrams/DeleteModelDiagram.puml new file mode 100644 index 00000000000..487a1072cbc --- /dev/null +++ b/docs/diagrams/DeleteModelDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as Logic LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> Logic : execute() +activate Logic + +Logic -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate --> UniquePersonList : contains(p) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson(p) +activate p + +p --> internalList +deactivate p + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> Logic +deactivate ModelManager + +Logic -> ModelManager : deletePerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: deletePerson(p) +activate ImmuniMate + +ImmuniMate -> UniquePersonList : remove(p) +activate UniquePersonList + +UniquePersonList -> internalList : remove(p) +activate internalList + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> Logic +deactivate ModelManager + +[<--Logic +deactivate Logic + +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..d8bbded95b4 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant "r:CommandResult" as CommandResult LOGIC_COLOR @@ -14,20 +14,20 @@ box Model MODEL_COLOR_T1 participant "m:Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete S1234567X") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> ImmuniMateParser : parseCommand("delete S1234567X") +activate ImmuniMateParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +ImmuniMateParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> ImmuniMateParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +ImmuniMateParser -> DeleteCommandParser : parse("S1234567X") activate DeleteCommandParser create DeleteCommand @@ -37,22 +37,25 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> ImmuniMateParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> ImmuniMateParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +ImmuniMateParser --> LogicManager : d +deactivate ImmuniMateParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : hasPerson(...) activate Model - -Model --> DeleteCommand +Model --> DeleteCommand : true +deactivate Model +DeleteCommand -> Model : deletePerson(...) +activate Model +Model --> DeleteCommand : delete specified person deactivate Model create CommandResult diff --git a/docs/diagrams/FindLogicModelDiagram.puml b/docs/diagrams/FindLogicModelDiagram.puml new file mode 100644 index 00000000000..b4a0b230803 --- /dev/null +++ b/docs/diagrams/FindLogicModelDiagram.puml @@ -0,0 +1,59 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "f:FindCommand" as FindCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("find ...") +activate LogicManager +LogicManager -> ImmuniMateParser : parseCommand("find ...") +activate ImmuniMateParser + +create FindCommandParser +ImmuniMateParser -> FindCommandParser +activate FindCommandParser +FindCommandParser --> ImmuniMateParser +deactivate FindCommandParser +ImmuniMateParser -> FindCommandParser : parse("...") +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand +activate FindCommand +FindCommand --> FindCommandParser +deactivate FindCommand +FindCommandParser --> ImmuniMateParser : f +deactivate FindCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCommandParser -[hidden]-> ImmuniMateParser +destroy FindCommandParser +ImmuniMateParser --> LogicManager : f +deactivate ImmuniMateParser +LogicManager -> FindCommand : execute(m) +activate FindCommand +FindCommand -> Model : updateFilteredPersonList(...) +activate Model +Model --> FindCommand +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult +CommandResult --> FindCommand +deactivate +FindCommand --> LogicManager : r +deactivate FindCommand + +[<--LogicManager : r +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..e65d736d024 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,20 +5,33 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model as ModelPackage <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyImmuniMate" as ReadOnlyImmuniMate Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model -Class AddressBook +Class ImmuniMate Class ModelManager Class UserPrefs - Class UniquePersonList +Class UniqueVisitList Class Person Class Address -Class Email Class Name Class Phone -Class Tag +Class Nric +Class DateOfBirth +Class Sex +Class Status +Class Email +Class Country +Class Allergies +Class BloodType +Class Condition +Class DateOfAdmission +Class Diagnosis +Class Symptom +Class Visit +Class DateOfVisit + Class I #FFFFFF } @@ -26,29 +39,54 @@ Class I #FFFFFF Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +ImmuniMate .up.|> ReadOnlyImmuniMate ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyImmuniMate +ModelManager -left-> "1" ImmuniMate ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList +ImmuniMate *--> "1" UniquePersonList +ImmuniMate *--> "1" UniqueVisitList UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +UniqueVisitList --> "~* all" Visit +Person *--> "1" Nric +Person *--> "1" Name +Person *--> "1" Phone +Person *--> "1" Address +Person *--> "1" DateOfBirth +Person *--> "1" Sex +Person *--> "1" Status + +Person *--> "0..1" Email +Person *--> "0..1" Country +Person *--> "0..1" Allergies +Person *--> "0..1" BloodType +Person *--> "0..1" Condition +Person *--> "0..1" DateOfAdmission +Person *--> "0..1" Diagnosis +Person *--> "0..1" Symptom + +Visit *--> "1" DateOfVisit +Visit *--> "1" Nric +Visit *--> "1" Diagnosis +Visit *--> "1" Symptom +Visit *--> "1" Status + Person -[hidden]up--> I UniquePersonList -[hidden]right-> I +Nric -[hidden]right-> Name Name -[hidden]right-> Phone Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Address -[hidden]right-> DateOfBirth +DateOfBirth -[hidden]right-> Sex +Sex -[hidden]right-> Status ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" Visit @enduml + diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index ce4c5ce8c8d..2080e0b626f 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 ImmuniMateParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> ImmuniMateParser -AddressBookParser .down.> XYZCommandParser: <> +ImmuniMateParser .down.> XYZCommandParser: <> XYZCommandParser ..> XYZCommand : <> -AddressBookParser ..> Command : <> +ImmuniMateParser ..> Command : <> XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/ReadCommandModelDiagram.puml b/docs/diagrams/ReadCommandModelDiagram.puml new file mode 100644 index 00000000000..6c464882976 --- /dev/null +++ b/docs/diagrams/ReadCommandModelDiagram.puml @@ -0,0 +1,59 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as ReadCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> ReadCommand : execute() +activate ReadCommand + +ReadCommand -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate --> UniquePersonList : contains(p) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson(p) +activate p + +p --> internalList +deactivate p + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> ReadCommand +deactivate ModelManager + +ReadCommand -> ModelManager : updateFilteredPersonList(predicate) +activate ModelManager + +ModelManager --> ReadCommand +deactivate ModelManager + +[<--ReadCommand +deactivate ReadCommand + +@enduml diff --git a/docs/diagrams/ReadCommandSequenceDiagram.puml b/docs/diagrams/ReadCommandSequenceDiagram.puml new file mode 100644 index 00000000000..f2ea9749383 --- /dev/null +++ b/docs/diagrams/ReadCommandSequenceDiagram.puml @@ -0,0 +1,82 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ReadCommandParser" as ReadCommandParser LOGIC_COLOR +participant "r:ReadCommand" as ReadCommand LOGIC_COLOR +participant "c:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("read T0123456A") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("read T0123456A") +activate AddressBookParser + +create ReadCommandParser +AddressBookParser -> ReadCommandParser +activate ReadCommandParser + +ReadCommandParser --> AddressBookParser +deactivate ReadCommandParser + +AddressBookParser -> ReadCommandParser : parse("T0123456A") +activate ReadCommandParser + +create ReadCommand +ReadCommandParser -> ReadCommand +activate ReadCommand + +ReadCommand --> ReadCommandParser : +deactivate ReadCommand + +ReadCommandParser --> AddressBookParser : r +deactivate ReadCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ReadCommandParser -[hidden]-> AddressBookParser +destroy ReadCommandParser + +AddressBookParser --> LogicManager : r +deactivate AddressBookParser + +LogicManager -> ReadCommand : execute(m) +activate ReadCommand + +ReadCommand -> Model : hasPerson(...) +activate Model + +Model --> ReadCommand : true +deactivate Model + +ReadCommand -> Model : updateFilteredPersonList(...) +activate Model + +Model --> ReadCommand : updateFilteredPersonList() successful +deactivate Model + +ReadCommand -> Model : getFilteredPersonList() +activate Model + +Model --> ReadCommand : Filtered person list +deactivate Model + +create CommandResult +ReadCommand -> CommandResult +activate CommandResult + +CommandResult --> ReadCommand +deactivate CommandResult + +ReadCommand --> LogicManager : c +deactivate ReadCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..979f7e4d969 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,7 @@ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedVisit } } @@ -38,6 +38,6 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedVisit @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..a0368e7b17c 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -10,7 +10,7 @@ Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow Class HelpWindow -Class ResultDisplay +Class ResultPanel Class PersonListPanel Class PersonCard Class StatusBarFooter @@ -31,7 +31,7 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox -MainWindow *-down-> "1" ResultDisplay +MainWindow *-down-> "1" ResultPanel MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow @@ -40,7 +40,7 @@ PersonListPanel -down-> "*" PersonCard MainWindow -left-|> UiPart -ResultDisplay --|> UiPart +ResultPanel --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart @@ -53,8 +53,8 @@ MainWindow -left-> Logic PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox -CommandBox -[hidden]left- ResultDisplay -ResultDisplay -[hidden]left- StatusBarFooter +CommandBox -[hidden]left- ResultPanel +ResultPanel -[hidden]left- StatusBarFooter MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UpdateLogicDiagram.puml b/docs/diagrams/UpdateLogicDiagram.puml new file mode 100644 index 00000000000..d7327e3e4c1 --- /dev/null +++ b/docs/diagrams/UpdateLogicDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ImmuniMateParser" as ImmuniMateParser LOGIC_COLOR +participant ":UpdateCommandParser" as UpdateCommandParser LOGIC_COLOR +participant "u:UpdateCommand" as UpdateCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("update T0123456A ...") +activate LogicManager +LogicManager -> ImmuniMateParser : parseCommand("update T0123456A ...") +activate ImmuniMateParser + +create UpdateCommandParser +ImmuniMateParser -> UpdateCommandParser +activate UpdateCommandParser +UpdateCommandParser --> ImmuniMateParser +deactivate UpdateCommandParser +ImmuniMateParser -> UpdateCommandParser : parse("T0123456A ...") +activate UpdateCommandParser + +create UpdateCommand +UpdateCommandParser -> UpdateCommand +activate UpdateCommand +UpdateCommand --> UpdateCommandParser +deactivate UpdateCommand +UpdateCommandParser --> ImmuniMateParser : u +deactivate UpdateCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UpdateCommandParser -[hidden]-> ImmuniMateParser +destroy UpdateCommandParser +ImmuniMateParser --> LogicManager : u +deactivate ImmuniMateParser +LogicManager -> UpdateCommand : execute(m) +activate UpdateCommand +UpdateCommand -> Model : getFilteredPersonList() +activate Model +Model --> UpdateCommand : lastShownList +deactivate Model +UpdateCommand -> Model : hasPerson(...) +activate Model +Model --> UpdateCommand : true +deactivate Model +UpdateCommand -> Model : setPerson(...) +activate Model +Model --> UpdateCommand +deactivate Model +UpdateCommand -> Model : updateFilteredPersonList(...) +activate Model +Model --> UpdateCommand +deactivate Model + +create CommandResult +UpdateCommand -> CommandResult +activate CommandResult +CommandResult --> UpdateCommand +deactivate +UpdateCommand --> LogicManager : r +deactivate UpdateCommand + +[<--LogicManager : r +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UpdateModelDiagram.puml b/docs/diagrams/UpdateModelDiagram.puml new file mode 100644 index 00000000000..90007a5178d --- /dev/null +++ b/docs/diagrams/UpdateModelDiagram.puml @@ -0,0 +1,94 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant "l:Logic" as CreateCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant ":ImmuniMate" as ImmuniMate MODEL_COLOR +participant ":UniquePersonList" as UniquePersonList MODEL_COLOR +participant "internalList: ObservableList" as internalList MODEL_COLOR +participant "p:Person" as p MODEL_COLOR +end box + +[-> CreateCommand : execute() +activate CreateCommand + +CreateCommand -> ModelManager : getFilteredPersonList() +activate ModelManager + +ModelManager --> CreateCommand : filteredPersons +deactivate ModelManager + +CreateCommand -> ModelManager : hasPerson(p) +activate ModelManager + +ModelManager -> ImmuniMate: hasPerson(p) +activate ImmuniMate + +ImmuniMate --> UniquePersonList : contains(p) +activate UniquePersonList + +UniquePersonList -> internalList : stream().anyMatch(p::isSamePerson) +activate internalList + +internalList -> p : isSamePerson(p) +activate p + +p --> internalList : true +deactivate p + +internalList --> UniquePersonList : true +deactivate internalList + +UniquePersonList --> ImmuniMate : true +deactivate UniquePersonList + +ImmuniMate --> ModelManager : true +deactivate ImmuniMate + +ModelManager --> CreateCommand : true +deactivate ModelManager + +CreateCommand -> ModelManager : setPerson(p, u) +activate ModelManager + +ModelManager -> ImmuniMate: setPerson(p, u) +activate ImmuniMate + +ImmuniMate -> UniquePersonList : setPerson(p, u) +activate UniquePersonList + +UniquePersonList -> internalList : indexOf(p) +activate internalList + +internalList --> UniquePersonList : index +deactivate internalList + +UniquePersonList -> internalList : set(index, u) +activate internalList + +internalList --> UniquePersonList +deactivate internalList + +UniquePersonList --> ImmuniMate +deactivate UniquePersonList + +ImmuniMate --> ModelManager +deactivate ImmuniMate + +ModelManager --> CreateCommand +deactivate ModelManager + +CreateCommand -> ModelManager : updateFilteredPersonList() +activate ModelManager + +ModelManager --> CreateCommand +deactivate ModelManager + +[<--CreateCommand +deactivate CreateCommand + +@enduml diff --git a/docs/images/AddVisitCommandLogic.png b/docs/images/AddVisitCommandLogic.png new file mode 100644 index 00000000000..4e4cc93a6f1 Binary files /dev/null and b/docs/images/AddVisitCommandLogic.png differ diff --git a/docs/images/AddVisitCommandModel.png b/docs/images/AddVisitCommandModel.png new file mode 100644 index 00000000000..b3e68af4c82 Binary files /dev/null and b/docs/images/AddVisitCommandModel.png differ diff --git a/docs/images/AddVisitCommandResult.png b/docs/images/AddVisitCommandResult.png new file mode 100644 index 00000000000..1e19279aa5c Binary files /dev/null and b/docs/images/AddVisitCommandResult.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..b4c1070144f 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/CheckCommandModelDiagram.png b/docs/images/CheckCommandModelDiagram.png new file mode 100644 index 00000000000..9c344ca7eb5 Binary files /dev/null and b/docs/images/CheckCommandModelDiagram.png differ diff --git a/docs/images/CheckCommandResult.png b/docs/images/CheckCommandResult.png new file mode 100644 index 00000000000..babfe326a9d Binary files /dev/null and b/docs/images/CheckCommandResult.png differ diff --git a/docs/images/CheckCommandSequenceDiagram.png b/docs/images/CheckCommandSequenceDiagram.png new file mode 100644 index 00000000000..383bb95e103 Binary files /dev/null and b/docs/images/CheckCommandSequenceDiagram.png differ diff --git a/docs/images/ClusterCommandResult.png b/docs/images/ClusterCommandResult.png new file mode 100644 index 00000000000..d6f95830e43 Binary files /dev/null and b/docs/images/ClusterCommandResult.png differ diff --git a/docs/images/ClusterLogicModelDiagram.png b/docs/images/ClusterLogicModelDiagram.png new file mode 100644 index 00000000000..8d8e5204ab6 Binary files /dev/null and b/docs/images/ClusterLogicModelDiagram.png differ diff --git a/docs/images/CreateCommand.png b/docs/images/CreateCommand.png new file mode 100644 index 00000000000..104d05b93ce Binary files /dev/null and b/docs/images/CreateCommand.png differ diff --git a/docs/images/CreateCommandLogic.png b/docs/images/CreateCommandLogic.png new file mode 100644 index 00000000000..3dd51f2863d Binary files /dev/null and b/docs/images/CreateCommandLogic.png differ diff --git a/docs/images/CreateCommandModel.png b/docs/images/CreateCommandModel.png new file mode 100644 index 00000000000..acf5aff10ce Binary files /dev/null and b/docs/images/CreateCommandModel.png differ diff --git a/docs/images/CreateCommandResult.png b/docs/images/CreateCommandResult.png new file mode 100644 index 00000000000..c5470973be0 Binary files /dev/null and b/docs/images/CreateCommandResult.png differ diff --git a/docs/images/DeleteInfoModelDiagram.png b/docs/images/DeleteInfoModelDiagram.png new file mode 100644 index 00000000000..aaffa352df9 Binary files /dev/null and b/docs/images/DeleteInfoModelDiagram.png differ diff --git a/docs/images/DeleteInfoSequenceDiagram.png b/docs/images/DeleteInfoSequenceDiagram.png new file mode 100644 index 00000000000..bd61569a9bb Binary files /dev/null and b/docs/images/DeleteInfoSequenceDiagram.png differ diff --git a/docs/images/DeleteModelDiagram.png b/docs/images/DeleteModelDiagram.png new file mode 100644 index 00000000000..f428140fb11 Binary files /dev/null and b/docs/images/DeleteModelDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index ac2ae217c51..796654d7c23 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/ErrorMessage.png b/docs/images/ErrorMessage.png new file mode 100644 index 00000000000..06b32ce8c8d Binary files /dev/null and b/docs/images/ErrorMessage.png differ diff --git a/docs/images/FindAddressCommandResult.png b/docs/images/FindAddressCommandResult.png new file mode 100644 index 00000000000..59d857f3dec Binary files /dev/null and b/docs/images/FindAddressCommandResult.png differ diff --git a/docs/images/FindConditionCommandResult.png b/docs/images/FindConditionCommandResult.png new file mode 100644 index 00000000000..11ef89f4ded Binary files /dev/null and b/docs/images/FindConditionCommandResult.png differ diff --git a/docs/images/FindLogicModelDiagram.png b/docs/images/FindLogicModelDiagram.png new file mode 100644 index 00000000000..d9bc1e5cc83 Binary files /dev/null and b/docs/images/FindLogicModelDiagram.png differ diff --git a/docs/images/FindNameCommandResult.png b/docs/images/FindNameCommandResult.png new file mode 100644 index 00000000000..f080023770c Binary files /dev/null and b/docs/images/FindNameCommandResult.png differ diff --git a/docs/images/GUI.png b/docs/images/GUI.png new file mode 100644 index 00000000000..e317b3213a9 Binary files /dev/null and b/docs/images/GUI.png differ diff --git a/docs/images/GUIDetailed.png b/docs/images/GUIDetailed.png new file mode 100644 index 00000000000..53cc9fde18a Binary files /dev/null and b/docs/images/GUIDetailed.png differ diff --git a/docs/images/GithubReleasePage.png b/docs/images/GithubReleasePage.png new file mode 100644 index 00000000000..f7bbc29d3b7 Binary files /dev/null and b/docs/images/GithubReleasePage.png differ diff --git a/docs/images/JavaWebsite.png b/docs/images/JavaWebsite.png new file mode 100644 index 00000000000..1053bafe87e Binary files /dev/null and b/docs/images/JavaWebsite.png differ diff --git a/docs/images/JohnDoeUi.png b/docs/images/JohnDoeUi.png new file mode 100644 index 00000000000..1d39435982d Binary files /dev/null and b/docs/images/JohnDoeUi.png differ diff --git a/docs/images/KeyboardArrowKeys.png b/docs/images/KeyboardArrowKeys.png new file mode 100644 index 00000000000..6550cb80ebc Binary files /dev/null and b/docs/images/KeyboardArrowKeys.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..dd54cbde4cb 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index 2caeeb1a067..67f98a8318c 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/ReadCommandModelDiagram.png b/docs/images/ReadCommandModelDiagram.png new file mode 100644 index 00000000000..7b2e05286a8 Binary files /dev/null and b/docs/images/ReadCommandModelDiagram.png differ diff --git a/docs/images/ReadCommandResult.png b/docs/images/ReadCommandResult.png new file mode 100644 index 00000000000..17ed0f922bd Binary files /dev/null and b/docs/images/ReadCommandResult.png differ diff --git a/docs/images/ReadCommandSequenceDiagram.png b/docs/images/ReadCommandSequenceDiagram.png new file mode 100644 index 00000000000..a405af3b4a6 Binary files /dev/null and b/docs/images/ReadCommandSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..4ecd0cfee32 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..f2e9f0d5b24 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..3fe6df6d859 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UpdateCommandResult.png b/docs/images/UpdateCommandResult.png new file mode 100644 index 00000000000..8bf4a6ee58b Binary files /dev/null and b/docs/images/UpdateCommandResult.png differ diff --git a/docs/images/UpdateLogicDiagram.png b/docs/images/UpdateLogicDiagram.png new file mode 100644 index 00000000000..cac3a204a3c Binary files /dev/null and b/docs/images/UpdateLogicDiagram.png differ diff --git a/docs/images/UpdateModelDiagram.png b/docs/images/UpdateModelDiagram.png new file mode 100644 index 00000000000..e91a292b383 Binary files /dev/null and b/docs/images/UpdateModelDiagram.png differ diff --git a/docs/images/alex-setyawan.png b/docs/images/alex-setyawan.png new file mode 100644 index 00000000000..65b33e1f8b2 Binary files /dev/null and b/docs/images/alex-setyawan.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png deleted file mode 100644 index 235da1c273e..00000000000 Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..943833c61bc 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/jovantanyk.png b/docs/images/jovantanyk.png new file mode 100644 index 00000000000..05d51779e12 Binary files /dev/null and b/docs/images/jovantanyk.png differ diff --git a/docs/images/laney0808.png b/docs/images/laney0808.png new file mode 100644 index 00000000000..054148b25dc Binary files /dev/null and b/docs/images/laney0808.png differ diff --git a/docs/images/natleong.png b/docs/images/natleong.png new file mode 100644 index 00000000000..dcfc0a45a15 Binary files /dev/null and b/docs/images/natleong.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..743c2ecfa55 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,14 +1,14 @@ --- layout: page -title: AddressBook Level-3 +title: ImmuniMate --- [![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) -![Ui](images/Ui.png) +![Ui](images/GUI.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). +**ImmuniMate is a desktop application aimed at General Practitioners and Healthcare Professionals**. The application intends to assist users in managing patient information, and tracking potential infectious clusters. * 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. diff --git a/docs/team/johndoe.md b/docs/team/alex-setyawan.md similarity index 100% rename from docs/team/johndoe.md rename to docs/team/alex-setyawan.md diff --git a/docs/team/jovantanyk.md b/docs/team/jovantanyk.md new file mode 100644 index 00000000000..e86d1caef6c --- /dev/null +++ b/docs/team/jovantanyk.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Jovan Tan's Project Portfolio Page +--- + +### Project: AddressBook Level 3 + +AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/docs/team/laney0808.md b/docs/team/laney0808.md new file mode 100644 index 00000000000..cdc3a88c940 --- /dev/null +++ b/docs/team/laney0808.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Zhang Lanyu's Project Portfolio Page +--- + +### Project: AddressBook Level 3 + +AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to update patient's information in feature list. + * What it does: Allows user to update any field of any specified patient. + * Justification: This feature allows the user to update the patient's health condition and keep track of their contact information in a timely manner. + * Highlights: This feature enhances the convenience of usage as the user no longer need to delete the old record and key in new record with most of the information to be repeated. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/docs/team/natleong.md b/docs/team/natleong.md new file mode 100644 index 00000000000..c2a4bcc7178 --- /dev/null +++ b/docs/team/natleong.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Natalie Leong's Project Portfolio Page +--- + +### Project: AddressBook Level 3 + +AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to update patient's information in feature list. + * What it does: Allows user to update any field of any specified patient. + * Justification: This feature allows the user to update the patient's health condition and keep track of their contact information in a timely manner. + * Highlights: This feature enhances the convenience of usage as the user no longer need to delete the old record and key in new record with most of the information to be repeated. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..8ece82f970d 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,10 +15,10 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.ImmuniMate; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; @@ -75,8 +75,8 @@ public void init() throws Exception { private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { logger.info("Using data file : " + storage.getAddressBookFilePath()); - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional addressBookOptional; + ReadOnlyImmuniMate initialData; try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { @@ -87,7 +87,7 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { } catch (DataLoadingException e) { logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + initialData = new ImmuniMate(); } return new ModelManager(initialData, userPrefs); diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 100cb16c395..d934cf798c3 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -37,7 +37,8 @@ public class JsonUtil { .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .registerModule(new SimpleModule("SimpleModule") .addSerializer(Level.class, new ToStringSerializer()) - .addDeserializer(Level.class, new LevelDeserializer(Level.class))); + .addDeserializer(Level.class, new LevelDeserializer(Level.class)) + .addSerializer(new OptionalSerializer())); static void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException { FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize)); diff --git a/src/main/java/seedu/address/commons/util/OptionalSerializer.java b/src/main/java/seedu/address/commons/util/OptionalSerializer.java new file mode 100644 index 00000000000..11b0000713e --- /dev/null +++ b/src/main/java/seedu/address/commons/util/OptionalSerializer.java @@ -0,0 +1,30 @@ +package seedu.address.commons.util; + +import java.io.IOException; +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * A Serializer for handling Optional values and converting them into + * proper JSON strings with correct null formatting. + */ +public class OptionalSerializer extends JsonSerializer> { + @Override + public void serialize(Optional optional, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + if (optional.isPresent()) { + jsonGenerator.writeObject(optional.get()); + } else { + jsonGenerator.writeNull(); + } + } + /** + * Avoids directly specifying Optional.class with generics + */ + public Class> handledType() { + return (Class>) (Class) Optional.class; + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..55d7b2a1878 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,27 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code word}. + * 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 phrase cannot be null, cannot be empty, must be a single word + */ + public static boolean containsPhraseIgnoreCase(String sentence, String phrase) { + requireNonNull(sentence); + requireNonNull(phrase); + + String preppedPhrase = phrase.trim(); + checkArgument(!preppedPhrase.isEmpty(), "Word parameter cannot be empty"); + + return sentence.toLowerCase().contains(phrase.toLowerCase()); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..5c4124fe751 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -7,7 +7,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.person.Person; /** @@ -26,9 +26,9 @@ public interface Logic { /** * Returns the AddressBook. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getImmuniMate() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyImmuniMate getAddressBook(); /** 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 5aa3b91c7d0..dd20bd8fa23 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,10 +11,10 @@ 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.ImmuniMateParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -31,7 +31,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final ImmuniMateParser immuniMateParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -39,7 +39,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + immuniMateParser = new ImmuniMateParser(); } @Override @@ -47,11 +47,13 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredVisitList(Model.PREDICATE_SHOW_ALL_VISITS); + Command command = immuniMateParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveAddressBook(model.getImmuniMate()); } catch (AccessDeniedException e) { throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); } catch (IOException ioe) { @@ -62,8 +64,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyImmuniMate getAddressBook() { + return model.getImmuniMate(); } @Override @@ -73,7 +75,7 @@ public ObservableList getFilteredPersonList() { @Override public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + return model.getImmunimateFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..457390a362a 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -1,11 +1,13 @@ package seedu.address.logic; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; /** * Container for user visible messages. @@ -14,8 +16,10 @@ 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_PERSON_NOT_FOUND = "The person provided was not found"; + public static final String MESSAGE_VISIT_NOT_FOUND = "The visit provided was not found"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_VISITS_LISTED_OVERVIEW = "%1$d visits listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; @@ -36,15 +40,55 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref */ public static String format(Person person) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) - .append("; Phone: ") - .append(person.getPhone()) - .append("; Email: ") - .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + builder.append("Name: ").append(person.getName()).append("\n") + .append("NRIC: ").append(person.getNric()).append("\n") + .append("Phone: ").append(person.getPhone()).append("\n") + .append("Address: ").append(person.getAddress()).append("\n") + .append("DOB: ").append(person.getDateOfBirth()).append("\n") + .append("Sex: ").append(person.getSex()).append("\n") + .append("Status: ").append(person.getStatus()).append("\n") + .append("Email: ") + .append(Optional.ofNullable(person.getEmail()).map(Object::toString).orElse("-")).append("\n") + .append("Country: ") + .append(Optional.ofNullable(person.getCountry()).map(Object::toString).orElse("-")).append("\n") + .append("Allergies: ") + .append(Optional.ofNullable(person.getAllergies()).map(Object::toString).orElse("-")).append("\n") + .append("Blood Type: ") + .append(Optional.ofNullable(person.getBloodType()).map(Object::toString).orElse("-")).append("\n") + .append("Condition: ") + .append(Optional.ofNullable(person.getCondition()).map(Object::toString).orElse("-")).append("\n") + .append("DOA: ") + .append(Optional.ofNullable(person.getDateOfAdmission()).map(Object::toString).orElse("-")) + .append("\n") + .append("Diagnosis: ") + .append(Optional.ofNullable(person.getDiagnosis()).map(Object::toString).orElse("-")).append("\n") + .append("Symptom: ") + .append(Optional.ofNullable(person.getSymptom()).map(Object::toString).orElse("-")).append("\n"); + return builder.toString(); + } + + /** + * Formats the {@code visit} for display to the user. + */ + public static String format(Visit visit) { + final StringBuilder builder = new StringBuilder(); + builder.append("NRIC: ").append(visit.getNric()).append("\n") + .append("DOV: ").append(visit.getDateOfVisit()).append("\n") + .append("Symptom: ").append(visit.getSymptom()).append("\n") + .append("Diagnosis: ").append(visit.getDiagnosis()).append("\n") + .append("Status: ").append(visit.getStatus()).append("\n"); + return builder.toString(); + } + + /** + * Formats the {@code visit} for display to the user for check command. + */ + public static String formatCheck(Visit visit) { + final StringBuilder builder = new StringBuilder(); + builder.append("DOV: ").append(visit.getDateOfVisit()).append("\n") + .append("Symptom: ").append(visit.getSymptom()).append("\n") + .append("Diagnosis: ").append(visit.getDiagnosis()).append("\n") + .append("Status: ").append(visit.getStatus()).append("\n"); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddVisitCommand.java b/src/main/java/seedu/address/logic/commands/AddVisitCommand.java new file mode 100644 index 00000000000..b0ce0b45469 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddVisitCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFVISIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; + +/** + * Adds a person to the address book. + */ +public class AddVisitCommand extends Command { + public static final String COMMAND_WORD = "addvisit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nAdds a visit to a patient." + + "\nParameters: " + + PREFIX_NRIC + "NRIC " + + PREFIX_DATEOFVISIT + "DATEOFVISIT " + + PREFIX_SYMPTOM + "SYMPTOM " + + PREFIX_DIAGNOSIS + "DIAGNOSIS " + + PREFIX_STATUS + "STATUS " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NRIC + "S0123456A " + + PREFIX_DATEOFVISIT + "2024-01-04 " + + PREFIX_SYMPTOM + "Fever, Rhinorrhea " + + PREFIX_DIAGNOSIS + "Common Flu " + + PREFIX_STATUS + "PENDING"; + + public static final String MESSAGE_SUCCESS = "New Visit added: %1$s"; + public static final String MESSAGE_DUPLICATE_VISIT = "This visit already exists in the system"; + public static final String MESSAGE_INVALID_VISIT = "The NRIC supplied does not link to any existing Patient"; + + private final Visit toAdd; + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public AddVisitCommand(Visit visit) { + requireNonNull(visit); + toAdd = visit; + } + + //TODO test cases + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Nric patientNric = toAdd.getNric(); + Person patient = Person.createPersonWithNric(patientNric); + // Guard clauses to ensure NRIC is valid and Visit is not duplicate + if (!model.hasPerson(patient)) { + throw new CommandException(MESSAGE_INVALID_VISIT); + } + if (model.hasVisit(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_VISIT); + } + //TODO: Update patient symptom and diagnosis to reflect latest visit! + model.addVisit(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddVisitCommand)) { + return false; + } + + AddVisitCommand otherAddVisitCommand = (AddVisitCommand) other; + return toAdd.equals(otherAddVisitCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CheckCommand.java b/src/main/java/seedu/address/logic/commands/CheckCommand.java new file mode 100644 index 00000000000..72307a160be --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Nric; +import seedu.address.model.person.NricContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; +import seedu.address.model.visit.VisitContainsNricPredicate; + +/** + * Checks the visits in history of an existing person in the address book. + */ +public class CheckCommand extends Command { + + public static final String COMMAND_WORD = "check"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nChecks the visits in history of the person identified" + + " by the NRIC specified. " + + "\nExample: " + COMMAND_WORD + + " S0123456A"; + + public static final String MESSAGE_READ_PERSON_SUCCESS = "Checked Person ->\n%1$s"; + private final Nric nric; + + /** + * @param nric of the person to check + */ + public CheckCommand(Nric nric) { + requireNonNull(nric); + + this.nric = nric; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasPerson(Person.createPersonWithNric(nric))) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + + model.updateFilteredPersonList(new NricContainsKeywordsPredicate(nric.toString())); + Person checkedPerson = model.getFilteredPersonList().get(0); + + model.updateFilteredVisitList(new VisitContainsNricPredicate(nric.toString())); + List filteredVisits = model.getFilteredVisitList(); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(checkedPerson.getName()).append(" (NRIC: ").append(checkedPerson.getNric()).append("):\n"); + for (Visit visit : filteredVisits) { + stringBuilder.append(Messages.formatCheck(visit)).append("\n"); + } + + return new CommandResult(String.format(MESSAGE_READ_PERSON_SUCCESS, stringBuilder)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CheckCommand)) { + return false; + } + + CheckCommand otherCheckCommand = (CheckCommand) other; + return this.nric.equals(otherCheckCommand.nric); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("nric", nric) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(nric); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..5a6adebbccf 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,7 +2,7 @@ import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; +import seedu.address.model.ImmuniMate; import seedu.address.model.Model; /** @@ -17,7 +17,7 @@ public class ClearCommand extends Command { @Override public CommandResult execute(Model model) { requireNonNull(model); - model.setAddressBook(new AddressBook()); + model.setImmuniMate(new ImmuniMate()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/ClusterCommand.java b/src/main/java/seedu/address/logic/commands/ClusterCommand.java new file mode 100644 index 00000000000..219ecf177ea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClusterCommand.java @@ -0,0 +1,90 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.AddressDiagnosisStatusPredicate; + + +/** + * Updates the details of an existing person in the address book. + */ +public class ClusterCommand extends Command { + + public static final String COMMAND_WORD = "cluster"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nDetects if there is a cluster of the size given," + + " at the location given, of the disease given, and shows the details of all there with the disease.\n" + + "Parameters: [CLUSTER SIZE] " + + "[" + PREFIX_ADDRESS + "LOCATION] " + + "[" + PREFIX_DIAGNOSIS + "DISEASE]\n" + + "Example: " + COMMAND_WORD + " 50 " + + PREFIX_ADDRESS + "choa chu kang " + + PREFIX_DIAGNOSIS + "dengue"; + + public static final String MESSAGE_NO_INFECTED_PEOPLE = "Good news! This area \"%1$s\" has no patients with %2$s."; + public static final String MESSAGE_CLUSTER_FOUND_SUCCESS = + "Cluster of %1$s found!\nHere's everyone in the area of \"%2$s\" with \"%3$s\":"; + public static final String MESSAGE_CLUSTER_NOT_FOUND = "Cluster not found.\n" + + "There are only %1$s people in the area of \"%2$s\" with \"%3$s\".\n" + + "But here are infected people in the area to look out for:"; + private final int clusterSize; + private final AddressDiagnosisStatusPredicate predicate; + + /** + * @param clusterSize of the person in the filtered person list to update + * @param predicate details to update the person with + */ + public ClusterCommand(int clusterSize, AddressDiagnosisStatusPredicate predicate) { + requireNonNull(predicate); + + this.clusterSize = clusterSize; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + + int len = model.getFilteredPersonList().size(); + if (len == 0) { + return new CommandResult(String.format(MESSAGE_NO_INFECTED_PEOPLE, + predicate.getAddress(), predicate.getDisease())); + } else if (len < clusterSize) { + return new CommandResult(String.format(MESSAGE_CLUSTER_NOT_FOUND, len, + predicate.getAddress(), predicate.getDisease())); + } else { + return new CommandResult(String.format(MESSAGE_CLUSTER_FOUND_SUCCESS, len, + predicate.getAddress(), predicate.getDisease())); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClusterCommand)) { + return false; + } + + ClusterCommand otherClusterCommand = (ClusterCommand) other; + return otherClusterCommand.clusterSize == clusterSize + && predicate.equals(otherClusterCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("cluster size", clusterSize) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/CreateCommand.java similarity index 53% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/address/logic/commands/CreateCommand.java index 5d7185a9680..aeba6ea03b6 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/CreateCommand.java @@ -2,10 +2,18 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFBIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; 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_NRIC; 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_SEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -16,38 +24,45 @@ /** * Adds a person to the address book. */ -public class AddCommand extends Command { +public class CreateCommand extends Command { + public static final String COMMAND_WORD = "create"; - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nCreates a person in Immunimate. " + + "\nParameters: " + + PREFIX_NRIC + "NRIC " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " + + PREFIX_DATEOFBIRTH + "DATEOFBIRTH " + + PREFIX_SEX + "SEX " + + PREFIX_STATUS + "STATUS " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ALLERGIES + "ALLERGIES " + + PREFIX_BLOODTYPE + "BLOODTYPE " + + PREFIX_CONDITION + "CONDITION " + + PREFIX_SYMPTOM + "SYMPTOM " + + PREFIX_DIAGNOSIS + "DIAGNOSIS " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NRIC + "S0123456A " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_DATEOFBIRTH + "1990-01-01 " + + PREFIX_SEX + "M " + + PREFIX_STATUS + "PENDING "; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New patient added ->\n%1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the system"; private final Person toAdd; /** * Creates an AddCommand to add the specified {@code Person} */ - public AddCommand(Person person) { + public CreateCommand(Person person) { requireNonNull(person); toAdd = person; } - @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); @@ -67,12 +82,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof AddCommand)) { + if (!(other instanceof CreateCommand)) { return false; } - AddCommand otherAddCommand = (AddCommand) other; - return toAdd.equals(otherAddCommand.toAdd); + CreateCommand otherCreateCommand = (CreateCommand) other; + return toAdd.equals(otherCreateCommand.toAdd); } @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..adb260e090e 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -2,13 +2,12 @@ import static java.util.Objects.requireNonNull; -import java.util.List; - -import seedu.address.commons.core.index.Index; +import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; /** @@ -19,28 +18,28 @@ 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"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + + ":\nDeletes the person identified by the NRIC.\n" + + "Parameters: NRIC\n" + + "Example: " + COMMAND_WORD + " S0123456A"; - private final Index targetIndex; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person ->\n%1$s"; - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + private final Nric targetNric; + //TODO test cases + public DeleteCommand(Nric targetNric) { + this.targetNric = targetNric; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + ObservableList persons = model.getFilteredPersonList(); + if (!model.hasPerson(Person.createPersonWithNric(targetNric))) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); + //Difference between filteredPersons.contains and model.hasPerson: first checks if the instance is in the list, + //second checks if the NRIC is in the list + Person personToDelete = persons.filtered(person -> person.getNric().equals(targetNric)).get(0); model.deletePerson(personToDelete); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } @@ -57,13 +56,14 @@ public boolean equals(Object other) { } DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); + return targetNric.equals(otherDeleteCommand.targetNric); } + //TODO test cases @Override public String toString() { return new ToStringBuilder(this) - .add("targetIndex", targetIndex) + .add("targetNric", targetNric) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteInfoCommand.java b/src/main/java/seedu/address/logic/commands/DeleteInfoCommand.java new file mode 100644 index 00000000000..113437ef9d0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteInfoCommand.java @@ -0,0 +1,126 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; + +import javafx.collections.ObservableList; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; + +/** + * Deletes the information of a person identified using it's NRIC from the address book. + */ +public class DeleteInfoCommand extends Command { + /** + * The command word. + */ + public static final String COMMAND_WORD = "deleteinfo"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ":\nDeletes the information of the person identified by NRIC.\n" + + "Parameters: NRIC, fields to be deleted\n" + + "Example: " + COMMAND_WORD + " S1234567B " + PREFIX_EMAIL; + public static final String MESSAGE_ONLY_OPTIONAL_FIELDS = "Only optional fields can be deleted."; + /** + * The correspondance of optional field with their position in fieldstoDelete array. + */ + public enum Fields { + EMAIL, ALLERGIES, BLOODTYPE, DATEOFADMISSION, COUNTRY, CONDITION, SYMPTOM, DIAGNOSIS + } + public static final String MESSAGE_DELETE_PERSON_INFORMATION_SUCCESS = "Deleted Patient info: %1$s"; + public static final int NUM_FIELDS = 8; + + private final Nric targetNric; + //{email, allergies, bloodtype, date of admission, country, condition, symptom, diagnosis} + private boolean[] fieldsToDelete = new boolean[NUM_FIELDS]; + /** + * Creates a DeleteInfoCommand to delete the specified {@code Person}'s information + */ + public DeleteInfoCommand(Nric targetNric, boolean[] fieldsToDelete) { + requireNonNull(targetNric); + requireNonNull(fieldsToDelete); + this.targetNric = targetNric; + this.fieldsToDelete = fieldsToDelete; + } + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList persons = model.getFilteredPersonList(); + if (!model.hasPerson(Person.createPersonWithNric(targetNric))) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + //Difference between filteredPersons.contains and model.hasPerson: first checks if the instance is in the list, + //second checks if the NRIC is in the list + Person personToChange = persons.filtered(person -> person.getNric().equals(targetNric)).get(0); + if (fieldsToDelete[Fields.EMAIL.ordinal()]) { + personToChange.setEmail(null); + } + if (fieldsToDelete[Fields.ALLERGIES.ordinal()]) { + personToChange.setAllergies(null); + } + if (fieldsToDelete[Fields.BLOODTYPE.ordinal()]) { + personToChange.setBloodType(null); + } + if (fieldsToDelete[Fields.DATEOFADMISSION.ordinal()]) { + personToChange.setDateOfAdmission(null); + } + if (fieldsToDelete[Fields.COUNTRY.ordinal()]) { + personToChange.setCountry(null); + } + if (fieldsToDelete[Fields.CONDITION.ordinal()]) { + personToChange.setCondition(null); + } + if (fieldsToDelete[Fields.SYMPTOM.ordinal()]) { + personToChange.setSymptom(null); + } + if (fieldsToDelete[Fields.DIAGNOSIS.ordinal()]) { + personToChange.setDiagnosis(null); + } + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_INFORMATION_SUCCESS, + Messages.format(personToChange))); + } + + private boolean fieldsToDeleteEquals(boolean[] otherFieldsToDelete) { + if (otherFieldsToDelete.length != NUM_FIELDS) { + return false; + } + for (int i = 0; i < NUM_FIELDS; i++) { + if (this.fieldsToDelete[i] != otherFieldsToDelete[i]) { + return false; + } + } + return true; + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteInfoCommand)) { + return false; + } + + DeleteInfoCommand otherDeleteCommand = (DeleteInfoCommand) other; + return targetNric.equals(otherDeleteCommand.targetNric) + && fieldsToDeleteEquals(otherDeleteCommand.fieldsToDelete); + } + @Override + public String toString() { + StringBuilder fields = new StringBuilder(); + for (int i = 0; i < NUM_FIELDS; i++) { + if (fieldsToDelete[i]) { + fields.append(Fields.values()[i].toString()).append(","); + } + } + return new ToStringBuilder(this) + .add("targetNric", targetNric) + .add("fieldsToDelete", fields) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 4b581c7331e..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,242 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -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. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_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."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) - .toString(); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * 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(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) - && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..f3b652c744e 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,11 +1,16 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.function.Predicate; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. @@ -15,14 +20,18 @@ 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 + ":\nFinds all persons" + + " whose names, addresses or conditions contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Example (name): " + COMMAND_WORD + " " + PREFIX_NAME + "alice bob charlie" + + "\nExample (address): " + COMMAND_WORD + " " + PREFIX_ADDRESS + "tampines street, geylang road" + + "\nExample (condition): " + COMMAND_WORD + " " + PREFIX_CONDITION + "diabetes, high blood pressure"; - private final NameContainsKeywordsPredicate predicate; + private final Predicate predicate; + //TODO: add nric contains keywords - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(Predicate predicate) { this.predicate = predicate; } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..854b43e7000 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -9,7 +9,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nShows program usage instructions.\n" + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; diff --git a/src/main/java/seedu/address/logic/commands/ReadCommand.java b/src/main/java/seedu/address/logic/commands/ReadCommand.java new file mode 100644 index 00000000000..6d253c1d582 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReadCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Nric; +import seedu.address.model.person.NricContainsKeywordsPredicate; +import seedu.address.model.person.Person; + +/** + * Reads the details of an existing person in the address book. + */ +public class ReadCommand extends Command { + + public static final String COMMAND_WORD = "read"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nReads the details of the person identified " + + "by the NRIC specified. " + + "\nParameters: NRIC" + + "\nExample: " + COMMAND_WORD + + " S0123456A"; + + public static final String MESSAGE_READ_PERSON_SUCCESS = "Read Person ->\n%1$s"; + private final Nric nric; + + /** + * @param nric of the person to read + */ + public ReadCommand(Nric nric) { + requireNonNull(nric); + + this.nric = nric; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasPerson(Person.createPersonWithNric(nric))) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + + model.updateFilteredPersonList(new NricContainsKeywordsPredicate(nric.toString())); + Person readPerson = model.getFilteredPersonList().get(0); + + return new CommandResult(String.format(MESSAGE_READ_PERSON_SUCCESS, Messages.format(readPerson))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ReadCommand)) { + return false; + } + + ReadCommand otherReadCommand = (ReadCommand) other; + return this.nric.equals(otherReadCommand.nric); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("nric", nric) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UpdateCommand.java b/src/main/java/seedu/address/logic/commands/UpdateCommand.java new file mode 100644 index 00000000000..e41aead0df0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UpdateCommand.java @@ -0,0 +1,405 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFADMISSION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFBIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +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_SEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Address; +import seedu.address.model.person.Allergies; +import seedu.address.model.person.BloodType; +import seedu.address.model.person.Condition; +import seedu.address.model.person.Country; +import seedu.address.model.person.DateOfAdmission; +import seedu.address.model.person.DateOfBirth; +import seedu.address.model.person.Diagnosis; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.NricContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; + + +/** + * Updates the details of an existing person in the address book. + */ +public class UpdateCommand extends Command { + + public static final String COMMAND_WORD = "update"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\nUpdates the details of the person identified " + + "by the respective NRIC in the displayed person list. " + + "\nExisting values will be overwritten by the input values." + + "\nParameters: NRIC " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_DATEOFBIRTH + "DATEOFBIRTH] " + + "[" + PREFIX_SEX + "SEX] " + + "[" + PREFIX_STATUS + "STATUS] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_COUNTRY + "COUNTRY] " + + "[" + PREFIX_ALLERGIES + "ALLERGIES] " + + "[" + PREFIX_BLOODTYPE + "BLOODTYPE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_CONDITION + "CONDITION] " + + "[" + PREFIX_DATEOFADMISSION + "DATEOFADMISSION] " + + "[" + PREFIX_DIAGNOSIS + "DIAGNOSIS] " + + "[" + PREFIX_SYMPTOM + "SYMPTOM] " + + "\nExample: " + COMMAND_WORD + " S0123456A " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + public static final String MESSAGE_UPDATE_PERSON_SUCCESS = "Updated Person ->\n%1$s"; + public static final String MESSAGE_NOT_UPDATED = "At least one field to update must be provided."; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + private final Nric nric; + private final UpdatePersonDescriptor updatePersonDescriptor; + + /** + * @param nric of the person in the filtered person list to update + * @param updatePersonDescriptor details to update the person with + */ + public UpdateCommand(Nric nric, UpdatePersonDescriptor updatePersonDescriptor) { + requireNonNull(nric); + requireNonNull(updatePersonDescriptor); + + this.nric = nric; + this.updatePersonDescriptor = new UpdatePersonDescriptor(updatePersonDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (!model.hasPerson(Person.createPersonWithNric(nric))) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + + Person personToUpdate = lastShownList.stream().filter( + new NricContainsKeywordsPredicate(nric.toString())).findFirst().get(); + Person updatedPerson = createUpdatedPerson(personToUpdate, updatePersonDescriptor); + + if (!(personToUpdate.isSamePerson(updatedPerson) && model.hasPerson(updatedPerson))) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToUpdate, updatedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_UPDATE_PERSON_SUCCESS, Messages.format(updatedPerson))); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToUpdate} + * updated with {@code updatePersonDescriptor}. + */ + private static Person createUpdatedPerson(Person personToUpdate, UpdatePersonDescriptor updatePersonDescriptor) { + assert personToUpdate != null; + Nric nric = personToUpdate.getNric(); + Name updatedName = updatePersonDescriptor.getName().orElse(personToUpdate.getName()); + Phone updatedPhone = updatePersonDescriptor.getPhone().orElse(personToUpdate.getPhone()); + Address updatedAddress = updatePersonDescriptor.getAddress().orElse(personToUpdate.getAddress()); + DateOfBirth updatedDob = updatePersonDescriptor.getDateOfBirth().orElse(personToUpdate.getDateOfBirth()); + Sex updatedSex = updatePersonDescriptor.getSex().orElse(personToUpdate.getSex()); + Status updatedStatus = updatePersonDescriptor.getStatus().orElse(personToUpdate.getStatus()); + + Email updatedEmail = updatePersonDescriptor.getEmail().orElse(personToUpdate.getEmail()); + Country updatedCountry = updatePersonDescriptor.getCountry().orElse(personToUpdate.getCountry()); + + Allergies updatedAllergies = updatePersonDescriptor.getAllergies().orElse(personToUpdate.getAllergies()); + BloodType updatedBloodType = updatePersonDescriptor.getBloodType().orElse(personToUpdate.getBloodType()); + Condition updatedCondition = updatePersonDescriptor.getCondition().orElse(personToUpdate.getCondition()); + DateOfAdmission updatedDateOfAdmission = + updatePersonDescriptor.getDateOfAdmission().orElse(personToUpdate.getDateOfAdmission()); + Diagnosis updatedDiagnosis = updatePersonDescriptor.getDiagnosis().orElse(personToUpdate.getDiagnosis()); + Symptom updatedSymptom = updatePersonDescriptor.getSymptom().orElse(personToUpdate.getSymptom()); + + Person p = new Person(nric, updatedName, updatedPhone, updatedAddress, updatedDob, updatedSex, updatedStatus); + p.setEmail(updatedEmail); + p.setCountry(updatedCountry); + p.setAllergies(updatedAllergies); + p.setBloodType(updatedBloodType); + p.setCondition(updatedCondition); + p.setDateOfAdmission(updatedDateOfAdmission); + p.setDiagnosis(updatedDiagnosis); + p.setSymptom(updatedSymptom); + return p; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UpdateCommand)) { + return false; + } + + UpdateCommand otherUpdateCommand = (UpdateCommand) other; + return nric.equals(otherUpdateCommand.nric) + && updatePersonDescriptor.equals(otherUpdateCommand.updatePersonDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("updatePersonDescriptor", updatePersonDescriptor) + .toString(); + } + + /** + * Stores the details to update the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class UpdatePersonDescriptor { + private Nric nric; + private Name name; + private Phone phone; + private Address address; + private DateOfBirth dateOfBirth; + private Sex sex; + private Status status; + // Optional fields + private Email email; + private Country country; + //Medical information + private Allergies allergies; + private BloodType bloodType; + private Condition condition; + private DateOfAdmission dateOfAdmission; + private Diagnosis diagnosis; + private Symptom symptom; + + public UpdatePersonDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public UpdatePersonDescriptor(UpdatePersonDescriptor toCopy) { + setNric(toCopy.nric); + setName(toCopy.name); + setPhone(toCopy.phone); + setAddress(toCopy.address); + setDateOfBirth(toCopy.dateOfBirth); + setSex(toCopy.sex); + setStatus(toCopy.status); + + setEmail(toCopy.email); + setCountry(toCopy.country); + + setAllergies(toCopy.allergies); + setBloodType(toCopy.bloodType); + setCondition(toCopy.condition); + setDateOfAdmission(toCopy.dateOfAdmission); + setDiagnosis(toCopy.diagnosis); + setSymptom(toCopy.symptom); + } + + /** + * Returns true if at least one field is updated. + */ + public boolean isAnyFieldUpdated() { + return CollectionUtil.isAnyNonNull(name, phone, address, sex, status, email, country, + allergies, bloodType, condition, dateOfAdmission, diagnosis, symptom); + } + + public void setNric(Nric nric) { + this.nric = nric; + } + + public Nric getNric() { + return nric; + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setDateOfBirth(DateOfBirth dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + + public Optional getDateOfBirth() { + return Optional.ofNullable(dateOfBirth); + } + + public void setSex(Sex sex) { + this.sex = sex; + } + + public Optional getSex() { + return Optional.ofNullable(sex); + } + + public void setStatus(Status status) { + this.status = status; + } + + public Optional getStatus() { + return Optional.ofNullable(status); + } + + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setCountry(Country country) { + this.country = country; + } + + public Optional getCountry() { + return Optional.ofNullable(country); + } + + public void setAllergies(Allergies allergies) { + this.allergies = allergies; + } + + public Optional getAllergies() { + return Optional.ofNullable(allergies); + } + + public void setBloodType(BloodType bloodType) { + this.bloodType = bloodType; + } + + public Optional getBloodType() { + return Optional.ofNullable(bloodType); + } + + public void setCondition(Condition condition) { + this.condition = condition; + } + + public Optional getCondition() { + return Optional.ofNullable(condition); + } + + public void setDateOfAdmission(DateOfAdmission dateOfAdmission) { + this.dateOfAdmission = dateOfAdmission; + } + + public Optional getDateOfAdmission() { + return Optional.ofNullable(dateOfAdmission); + } + + public void setDiagnosis(Diagnosis diagnosis) { + this.diagnosis = diagnosis; + } + + public Optional getDiagnosis() { + return Optional.ofNullable(diagnosis); + } + + public void setSymptom(Symptom symptom) { + this.symptom = symptom; + } + + public Optional getSymptom() { + return Optional.ofNullable(symptom); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UpdatePersonDescriptor)) { + return false; + } + + UpdatePersonDescriptor otherUpdatePersonDescriptor = (UpdatePersonDescriptor) other; + return Objects.equals(nric, otherUpdatePersonDescriptor.nric) + && Objects.equals(name, otherUpdatePersonDescriptor.name) + && Objects.equals(phone, otherUpdatePersonDescriptor.phone) + && Objects.equals(address, otherUpdatePersonDescriptor.address) + && Objects.equals(dateOfBirth, otherUpdatePersonDescriptor.dateOfBirth) + && Objects.equals(sex, otherUpdatePersonDescriptor.sex) + && Objects.equals(status, otherUpdatePersonDescriptor.status) + && Objects.equals(email, otherUpdatePersonDescriptor.email) + && Objects.equals(country, otherUpdatePersonDescriptor.country) + && Objects.equals(allergies, otherUpdatePersonDescriptor.allergies) + && Objects.equals(bloodType, otherUpdatePersonDescriptor.bloodType) + && Objects.equals(condition, otherUpdatePersonDescriptor.condition) + && Objects.equals(dateOfAdmission, otherUpdatePersonDescriptor.dateOfAdmission) + && Objects.equals(diagnosis, otherUpdatePersonDescriptor.diagnosis) + && Objects.equals(symptom, otherUpdatePersonDescriptor.symptom); + } + + @Override + public int hashCode() { + return Objects.hash(nric, name, phone, address, dateOfBirth, sex, status, email, country, + allergies, bloodType, condition, dateOfAdmission, diagnosis, symptom); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("nric", nric) + .add("name", name) + .add("phone", phone) + .add("address", address) + .add("date of birth", dateOfBirth) + .add("sex", sex) + .add("status", status) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 4ff1a97ed77..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,61 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddVisitCommandParser.java b/src/main/java/seedu/address/logic/parser/AddVisitCommandParser.java new file mode 100644 index 00000000000..8c3750b0ff9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddVisitCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFVISIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddVisitCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Diagnosis; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; +import seedu.address.model.visit.DateOfVisit; +import seedu.address.model.visit.Visit; + + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddVisitCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddVisitCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NRIC, PREFIX_DATEOFVISIT, + PREFIX_SYMPTOM, PREFIX_DIAGNOSIS, PREFIX_STATUS); + if (!arePrefixesPresent(argMultimap, PREFIX_NRIC, PREFIX_DATEOFVISIT, PREFIX_SYMPTOM, + PREFIX_DIAGNOSIS, PREFIX_STATUS) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddVisitCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NRIC, PREFIX_DATEOFVISIT, + PREFIX_SYMPTOM, PREFIX_DIAGNOSIS, PREFIX_STATUS); + Nric nric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()); + + + DateOfVisit dov = ParserUtil.parseDateOfVisit(argMultimap.getValue(PREFIX_DATEOFVISIT).get()); + Symptom symptom = ParserUtil.parseSymptom(argMultimap.getValue(PREFIX_SYMPTOM).get()); + Diagnosis diagnosis = ParserUtil.parseDiagnosis(argMultimap.getValue(PREFIX_DIAGNOSIS).get()); + Status status = ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).get()); + + Visit visit = new Visit(nric, dov, symptom, diagnosis, status); + + return new AddVisitCommand(visit); + } + + /** + * 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 21e26887a83..3a02098f36c 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -62,6 +62,18 @@ public String getPreamble() { return getValue(new Prefix("")).orElse(""); } + /** + * Returns true if the argument multimap contains a mapping for the specified prefix. + */ + public boolean contains(Prefix prefix) { + return argMultimap.containsKey(prefix); + } + /** + * Returns the number of elements in the argument multimap. + */ + public int length() { + return argMultimap.size(); + } /** * Throws a {@code ParseException} if any of the prefixes given in {@code prefixes} appeared more than * once among the arguments. diff --git a/src/main/java/seedu/address/logic/parser/CheckCommandParser.java b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java new file mode 100644 index 00000000000..7b623cc0a0c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.CheckCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Nric; + +/** + * Parses input arguments and creates a new ReadCommand object + */ +public class CheckCommandParser implements Parser { + /** + * Parses the given {@code String} of argument in the context of the ReadCommand + * and returns an ReadCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CheckCommand parse(String args) throws ParseException { + requireNonNull(args); + //ToUpperCase to be correctly implemented in model + String trimmedArg = args.trim(); + if (trimmedArg.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckCommand.MESSAGE_USAGE)); + } + + try { + return new CheckCommand(new Nric(trimmedArg.toUpperCase())); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format("%s", e.getMessage())); + } + } +} + + diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..d1457fe9876 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,20 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_NRIC = new Prefix("ic/"); + public static final Prefix PREFIX_DATEOFBIRTH = new Prefix("dob/"); + public static final Prefix PREFIX_SEX = new Prefix("s/"); + public static final Prefix PREFIX_PHONE = new Prefix("hp/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_COUNTRY = new Prefix("c/"); + public static final Prefix PREFIX_DATEOFADMISSION = new Prefix("doa/"); + public static final Prefix PREFIX_DATEOFVISIT = new Prefix("dov/"); + public static final Prefix PREFIX_BLOODTYPE = new Prefix("bt/"); + public static final Prefix PREFIX_ALLERGIES = new Prefix("al/"); + public static final Prefix PREFIX_CONDITION = new Prefix("con/"); + public static final Prefix PREFIX_SYMPTOM = new Prefix("sym/"); + public static final Prefix PREFIX_DIAGNOSIS = new Prefix("d/"); + public static final Prefix PREFIX_STATUS = new Prefix("st/"); } diff --git a/src/main/java/seedu/address/logic/parser/ClusterCommandParser.java b/src/main/java/seedu/address/logic/parser/ClusterCommandParser.java new file mode 100644 index 00000000000..1164dc0195d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ClusterCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; + +import seedu.address.logic.commands.ClusterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressDiagnosisStatusPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class ClusterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ClusterCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ClusterCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(trimmedArgs, PREFIX_ADDRESS, PREFIX_DIAGNOSIS); + + int clusterSize; + try { + clusterSize = Integer.parseInt(argMultimap.getPreamble()); + } catch (NumberFormatException nfe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ClusterCommand.MESSAGE_USAGE), nfe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ADDRESS, PREFIX_DIAGNOSIS); + if (clusterSize < 1 || clusterSize > 2000000000 + || !argMultimap.getValue(PREFIX_ADDRESS).isPresent() + || !argMultimap.getValue(PREFIX_DIAGNOSIS).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ClusterCommand.MESSAGE_USAGE)); + } + + String address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()).toString(); + String disease = ParserUtil.parseDiagnosis(argMultimap.getValue(PREFIX_DIAGNOSIS).get()).toString(); + return new ClusterCommand(clusterSize, + new AddressDiagnosisStatusPredicate(address, disease, "UNWELL")); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CreateCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateCommandParser.java new file mode 100644 index 00000000000..0692773d368 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CreateCommandParser.java @@ -0,0 +1,105 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFADMISSION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFBIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +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_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.CreateCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Address; +import seedu.address.model.person.DateOfBirth; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.Status; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class CreateCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + //TODO test cases + public CreateCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, + PREFIX_DATEOFBIRTH, PREFIX_SEX, PREFIX_STATUS, PREFIX_EMAIL, PREFIX_COUNTRY, + PREFIX_DATEOFADMISSION, PREFIX_ALLERGIES, PREFIX_BLOODTYPE, PREFIX_CONDITION, PREFIX_SYMPTOM, + PREFIX_DIAGNOSIS); + if (!arePrefixesPresent(argMultimap, PREFIX_NRIC, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_DATEOFBIRTH, + PREFIX_SEX, PREFIX_STATUS) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, + PREFIX_DATEOFBIRTH, PREFIX_SEX, PREFIX_STATUS); + Nric nric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + DateOfBirth dob = ParserUtil.parseDateOfBirth(argMultimap.getValue(PREFIX_DATEOFBIRTH).get()); + Sex sex = ParserUtil.parseSex(argMultimap.getValue(PREFIX_SEX).get()); + Status status = ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).get()); + //TODO (later): assersion to make sure optional values don't generate errors + Person person = new Person(nric, name, phone, address, dob, sex, status); + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + person.setEmail(email); + } + if (argMultimap.getValue(PREFIX_COUNTRY).isPresent()) { + person.setCountry(ParserUtil.parseCountry(argMultimap.getValue(PREFIX_COUNTRY).get())); + } + if (argMultimap.getValue(PREFIX_DATEOFADMISSION).isPresent()) { + person.setDateOfAdmission(ParserUtil.parseDateOfAdmission(argMultimap + .getValue(PREFIX_DATEOFADMISSION).get())); + } + if (argMultimap.getValue(PREFIX_ALLERGIES).isPresent()) { + person.setAllergies(ParserUtil.parseAllergies(argMultimap.getValue(PREFIX_ALLERGIES).get())); + } + if (argMultimap.getValue(PREFIX_BLOODTYPE).isPresent()) { + person.setBloodType(ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE).get())); + } + if (argMultimap.getValue(PREFIX_CONDITION).isPresent()) { + person.setCondition(ParserUtil.parseCondition(argMultimap.getValue(PREFIX_CONDITION).get())); + } + if (argMultimap.getValue(PREFIX_SYMPTOM).isPresent()) { + person.setSymptom(ParserUtil.parseSymptom(argMultimap.getValue(PREFIX_SYMPTOM).get())); + } + if (argMultimap.getValue(PREFIX_DIAGNOSIS).isPresent()) { + person.setDiagnosis(ParserUtil.parseDiagnosis(argMultimap.getValue(PREFIX_DIAGNOSIS).get())); + } + + return new CreateCommand(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/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..b51ed42d7e2 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -2,9 +2,9 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -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.Nric; /** * Parses input arguments and creates a new DeleteCommand object @@ -18,8 +18,8 @@ public class DeleteCommandParser implements Parser { */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + Nric nric = ParserUtil.parseNric(args); + return new DeleteCommand(nric); } catch (ParseException pe) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); diff --git a/src/main/java/seedu/address/logic/parser/DeleteInfoCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteInfoCommandParser.java new file mode 100644 index 00000000000..720f55759a3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteInfoCommandParser.java @@ -0,0 +1,98 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFADMISSION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFBIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +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_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; + +import java.util.Arrays; + +import seedu.address.logic.commands.DeleteInfoCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Nric; + + + + + +/** + * Parses input arguments and creates a new DeleteInfoCommand object + */ +public class DeleteInfoCommandParser implements Parser { + /** + * List of prefixes for optional fields, in the order of the fields in DeleteInfoCommand. + */ + public static final Prefix[] OPTIONAL_PREFIXES = {PREFIX_EMAIL, PREFIX_ALLERGIES, PREFIX_BLOODTYPE, + PREFIX_DATEOFADMISSION, PREFIX_COUNTRY, PREFIX_CONDITION, PREFIX_SYMPTOM, PREFIX_DIAGNOSIS}; + /** + * Parses the given {@code String} of arguments in the context of the DeleteInfoCommand + * and returns a DeleteInfoCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteInfoCommand parse(String args) throws ParseException { + //Check for empty input + if (args.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteInfoCommand.MESSAGE_USAGE)); + } + //Parse prefixes + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, + PREFIX_DATEOFBIRTH, PREFIX_SEX, PREFIX_STATUS, PREFIX_EMAIL, PREFIX_COUNTRY, + PREFIX_DATEOFADMISSION, PREFIX_ALLERGIES, PREFIX_BLOODTYPE, PREFIX_CONDITION, PREFIX_SYMPTOM, + PREFIX_DIAGNOSIS); + //Check for NRIC + Nric nric; + try { + nric = ParserUtil.parseNric(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteInfoCommand.MESSAGE_USAGE), pe); + } + + //Check for empty input + if (argMultimap.length() <= 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteInfoCommand.MESSAGE_USAGE)); + } + //check for mandatory fields + Prefix[] mandatoryPrefixes = {PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_DATEOFBIRTH, + PREFIX_SEX, PREFIX_STATUS}; + if (Arrays.stream(mandatoryPrefixes).anyMatch(argMultimap::contains)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteInfoCommand.MESSAGE_ONLY_OPTIONAL_FIELDS)); + } + + //Check for optional fields & any extra invalid input + boolean[] fieldsToDelete = new boolean[DeleteInfoCommand.NUM_FIELDS]; + Arrays.fill(fieldsToDelete, false); + Prefix[] optionalPrefixes = {PREFIX_EMAIL, PREFIX_ALLERGIES, + PREFIX_BLOODTYPE, PREFIX_DATEOFADMISSION, PREFIX_COUNTRY, + PREFIX_CONDITION, PREFIX_SYMPTOM, PREFIX_DIAGNOSIS}; + for (int i = 0; i < optionalPrefixes.length; i++) { + //prefix is not mentioned + if (argMultimap.getValue(optionalPrefixes[i]).isEmpty()) { + continue; + } + //prefix is mentioned but value is not empty + if (!argMultimap.getValue(optionalPrefixes[i]).get().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteInfoCommand.MESSAGE_USAGE)); + } + fieldsToDelete[i] = true; + } + return new DeleteInfoCommand(nric, fieldsToDelete); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 46b3309a78b..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,85 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..6b6502358f4 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,11 +1,17 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import java.util.Arrays; +import java.util.List; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressContainsKeywordsPredicate; +import seedu.address.model.person.ConditionContainsKeywordsPredicate; import seedu.address.model.person.NameContainsKeywordsPredicate; /** @@ -25,9 +31,47 @@ public FindCommand parse(String args) throws ParseException { String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + if (trimmedArgs.startsWith(PREFIX_NAME.getPrefix())) { + String nameKeywords = trimmedArgs.substring(2); + if (nameKeywords.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + List list = Arrays.asList(nameKeywords.trim().split("\\s+")); + return new FindCommand(new NameContainsKeywordsPredicate(list)); + } else if (trimmedArgs.startsWith(PREFIX_ADDRESS.getPrefix())) { + String doubleTrimmedArgs = trimmedArgs.substring(2); + if (doubleTrimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } + String[] addressKeywords = doubleTrimmedArgs.trim().split(","); + int len = addressKeywords.length; + for (int i = 0; i < len; i++) { + addressKeywords[i] = addressKeywords[i].trim(); + } + + List list = Arrays.asList(addressKeywords); + return new FindCommand(new AddressContainsKeywordsPredicate(list)); + } else if (trimmedArgs.startsWith(PREFIX_CONDITION.getPrefix())) { + String doubleTrimmedArgs = trimmedArgs.substring(4); + if (doubleTrimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + String[] conditionKeywords = doubleTrimmedArgs.trim().split(","); + int len = conditionKeywords.length; + for (int i = 0; i < len; i++) { + conditionKeywords[i] = conditionKeywords[i].trim(); + } + + List list = Arrays.asList(conditionKeywords); + return new FindCommand(new ConditionContainsKeywordsPredicate(list)); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/ImmuniMateParser.java similarity index 68% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/ImmuniMateParser.java index 3149ee07e0b..f04cf622d3b 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/ImmuniMateParser.java @@ -8,27 +8,32 @@ import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddVisitCommand; +import seedu.address.logic.commands.CheckCommand; import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.ClusterCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CreateCommand; import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.DeleteInfoCommand; 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.ReadCommand; +import seedu.address.logic.commands.UpdateCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses user input. */ -public class AddressBookParser { +public class ImmuniMateParser { /** * Used for initial separation of command word and args. */ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); + private static final Logger logger = LogsCenter.getLogger(ImmuniMateParser.class); /** * Parses user input into command for execution. @@ -37,6 +42,7 @@ public class AddressBookParser { * @return the command based on the user input * @throws ParseException if the user input does not conform the expected format */ + //TODO test cases public Command parseCommand(String userInput) throws ParseException { final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); if (!matcher.matches()) { @@ -53,29 +59,44 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case ClusterCommand.COMMAND_WORD: + return new ClusterCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case CreateCommand.COMMAND_WORD: + return new CreateCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); + case DeleteInfoCommand.COMMAND_WORD: + return new DeleteInfoCommandParser().parse(arguments); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + case ListCommand.COMMAND_WORD: return new ListCommand(); - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + case ReadCommand.COMMAND_WORD: + return new ReadCommandParser().parse(arguments); - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + case AddVisitCommand.COMMAND_WORD: + return new AddVisitCommandParser().parse(arguments); + + case CheckCommand.COMMAND_WORD: + return new CheckCommandParser().parse(arguments); + + case UpdateCommand.COMMAND_WORD: + return new UpdateCommandParser().parse(arguments); default: logger.finer("This user input caused a ParseException: " + userInput); diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..b3cd00b6d7c 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,29 +2,38 @@ import static java.util.Objects.requireNonNull; -import java.util.Collection; -import java.util.HashSet; -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.person.Address; +import seedu.address.model.person.Allergies; +import seedu.address.model.person.BloodType; +import seedu.address.model.person.Condition; +import seedu.address.model.person.Country; +import seedu.address.model.person.DateOfAdmission; +import seedu.address.model.person.DateOfBirth; +import seedu.address.model.person.Diagnosis; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Sex; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; +import seedu.address.model.visit.DateOfVisit; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { + //TODO: add parser for new fields public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -35,6 +44,23 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String nric} into a {@code Nric}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code nric} is invalid. + */ + //TODO test cases + public static Nric parseNric(String nric) throws ParseException { + requireNonNull(nric); + //Correct implementation for case-insensitive NRIC + String trimmedNric = nric.trim().toUpperCase(); + if (!Nric.isValidNric(trimmedNric)) { + throw new ParseException(Nric.MESSAGE_CONSTRAINTS); + } + return new Nric(trimmedNric.toUpperCase()); + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -56,6 +82,7 @@ public static Name parseName(String name) throws ParseException { * * @throws ParseException if the given {@code phone} is invalid. */ + //TODO test cases public static Phone parsePhone(String phone) throws ParseException { requireNonNull(phone); String trimmedPhone = phone.trim(); @@ -81,44 +108,187 @@ public static Address parseAddress(String address) throws ParseException { } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String dob} into an {@code DateOfBirth}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code dob} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + //TODO test cases + public static DateOfBirth parseDateOfBirth(String dob) throws ParseException { + requireNonNull(dob); + String trimmedDob = dob.trim(); + if (!DateOfBirth.isValidDateOfBirth(trimmedDob)) { + throw new ParseException(DateOfBirth.MESSAGE_CONSTRAINTS); } - return new Email(trimmedEmail); + return new DateOfBirth(trimmedDob); + } + + /** + * Parses a {@code String dov} into an {@code DateOfVisit}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code dov} is invalid. + */ + //TODO test cases + public static DateOfVisit parseDateOfVisit(String dov) throws ParseException { + requireNonNull(dov); + String trimmedDov = dov.trim(); + if (!DateOfVisit.isValidDateOfVisit(trimmedDov)) { + throw new ParseException(DateOfVisit.MESSAGE_CONSTRAINTS); + } + return new DateOfVisit(trimmedDov); + } + + /** + * Parses a {@code String sex} into an {@code Sex sex}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code sex} is invalid. + */ + //TODO test cases + public static Sex parseSex(String sex) throws ParseException { + requireNonNull(sex); + String trimmedSex = sex.trim(); + if (!Sex.isValidSex(trimmedSex)) { + throw new ParseException(Sex.MESSAGE_CONSTRAINTS); + } + return new Sex(trimmedSex); + } + + /** + * Parses a {@code String status} into an {@code Status status}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code sex} is invalid. + */ + //TODO test cases + public static Status parseStatus(String status) throws ParseException { + requireNonNull(status); + String trimmedStatus = status.trim().toUpperCase(); + if (!Status.isValidStatus(trimmedStatus)) { + throw new ParseException(Status.MESSAGE_CONSTRAINTS); + } + return new Status(trimmedStatus); + } + /** + * Parses a {@code String country} into an {@code Country country}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the given {@code country} is invalid. + */ + //TODO test cases + public static Country parseCountry(String country) throws ParseException { + requireNonNull(country); + String trimmedCountry = country.trim(); + if (!Country.isValidCountry(trimmedCountry)) { + throw new ParseException(Country.MESSAGE_CONSTRAINTS); + } + return new Country(trimmedCountry); + } + /** + * Parses a {@code String dateOfAdmission} into an {@code DateOfAdmission dateOfAdmission}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the given {@code dateOfAdmission} is invalid. + */ + //TODO test cases + public static DateOfAdmission parseDateOfAdmission(String dateOfAdmission) throws ParseException { + requireNonNull(dateOfAdmission); + String trimmedDateOfAdmission = dateOfAdmission.trim(); + if (!DateOfAdmission.isValidDateOfAdmission(trimmedDateOfAdmission)) { + throw new ParseException(DateOfAdmission.MESSAGE_CONSTRAINTS); + } + return new DateOfAdmission(trimmedDateOfAdmission); } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String status} into an {@code Status status}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code sex} is invalid. + */ + //TODO test cases + public static BloodType parseBloodType(String bloodType) throws ParseException { + requireNonNull(bloodType); + String trimmedBloodType = bloodType.trim(); + if (!BloodType.isValidBloodType(trimmedBloodType)) { + throw new ParseException(BloodType.MESSAGE_CONSTRAINTS); + } + return new BloodType(trimmedBloodType); + } + /** + * Parses a {@code String allergies} into an {@code Set}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the given {@code allergies} is invalid. + */ + //TODO test cases + public static Allergies parseAllergies(String allergies) throws ParseException { + requireNonNull(allergies); + String trimmedAllergies = allergies.trim(); + if (!Allergies.isValidAllergies(trimmedAllergies)) { + throw new ParseException(Allergies.MESSAGE_CONSTRAINTS); + } + return new Allergies(trimmedAllergies); + } + /** + * Parses a {@code String condition} into an {@code Condition}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code condition} is invalid. + */ + public static Condition parseCondition(String condition) throws ParseException { + requireNonNull(condition); + String trimmedCondition = condition.trim(); + if (!Condition.isValidCondition(trimmedCondition)) { + throw new ParseException(Condition.MESSAGE_CONSTRAINTS); + } + return new Condition(trimmedCondition); + } + /** + * Parses a {@code String symptom} into an {@code Symptom}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code symptom} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + //TODO test cases + public static Symptom parseSymptom(String symptom) throws ParseException { + requireNonNull(symptom); + String trimmedSymptom = symptom.trim(); + if (!Symptom.isValidSymptom(trimmedSymptom)) { + throw new ParseException(Symptom.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Symptom(trimmedSymptom); } + /** + * Parses a {@code String diagnosis} into an {@code Diagnosis}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code diagnosis} is invalid. + */ + //TODO test cases + public static Diagnosis parseDiagnosis(String diagnosis) throws ParseException { + requireNonNull(diagnosis); + String trimmedDiagnosis = diagnosis.trim(); + if (!Diagnosis.isValidDiagnosis(trimmedDiagnosis)) { + throw new ParseException(Diagnosis.MESSAGE_CONSTRAINTS); + } + return new Diagnosis(trimmedDiagnosis); + } + /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String email} into an {@code Email}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code email} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + //TODO test cases + public static Email parseEmail(String email) throws ParseException { + requireNonNull(email); + String trimmedEmail = email.trim(); + if (!Email.isValidEmail(trimmedEmail)) { + throw new ParseException(Email.MESSAGE_CONSTRAINTS); } - return tagSet; + return new Email(trimmedEmail); } + + } diff --git a/src/main/java/seedu/address/logic/parser/ReadCommandParser.java b/src/main/java/seedu/address/logic/parser/ReadCommandParser.java new file mode 100644 index 00000000000..6bbe1cbc99d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ReadCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ReadCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Nric; + +/** + * Parses input arguments and creates a new ReadCommand object + */ +public class ReadCommandParser implements Parser { + /** + * Parses the given {@code String} of argument in the context of the ReadCommand + * and returns an ReadCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ReadCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArg = args.trim(); + if (trimmedArg.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReadCommand.MESSAGE_USAGE)); + } + + try { + return new ReadCommand(new Nric(trimmedArg.toUpperCase())); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format("%s", e.getMessage())); + } + } +} + diff --git a/src/main/java/seedu/address/logic/parser/UpdateCommandParser.java b/src/main/java/seedu/address/logic/parser/UpdateCommandParser.java new file mode 100644 index 00000000000..a7ce46ed1c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UpdateCommandParser.java @@ -0,0 +1,121 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFADMISSION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATEOFBIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DIAGNOSIS; +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_NRIC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SYMPTOM; + +import seedu.address.logic.commands.UpdateCommand; +import seedu.address.logic.commands.UpdateCommand.UpdatePersonDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Nric; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class UpdateCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UpdateCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, + PREFIX_DATEOFBIRTH, PREFIX_SEX, PREFIX_STATUS, PREFIX_EMAIL, PREFIX_COUNTRY, + PREFIX_DATEOFADMISSION, PREFIX_ALLERGIES, PREFIX_BLOODTYPE, PREFIX_CONDITION, PREFIX_SYMPTOM, + PREFIX_DIAGNOSIS); + + Nric nric; + + try { + nric = ParserUtil.parseNric(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UpdateCommand.MESSAGE_USAGE), pe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NRIC, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, + PREFIX_DATEOFBIRTH, PREFIX_SEX, PREFIX_STATUS, PREFIX_EMAIL, PREFIX_COUNTRY, + PREFIX_DATEOFADMISSION, PREFIX_ALLERGIES, PREFIX_BLOODTYPE, PREFIX_CONDITION, PREFIX_SYMPTOM, + PREFIX_DIAGNOSIS); + + UpdatePersonDescriptor updatePersonDescriptor = new UpdatePersonDescriptor(); + updatePersonDescriptor.setNric(nric); + + // Mandatory fields + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + updatePersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + updatePersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + updatePersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_DATEOFBIRTH).isPresent()) { + updatePersonDescriptor.setDateOfBirth( + ParserUtil.parseDateOfBirth(argMultimap.getValue(PREFIX_DATEOFBIRTH).get())); + } + if (argMultimap.getValue(PREFIX_SEX).isPresent()) { + updatePersonDescriptor.setSex(ParserUtil.parseSex(argMultimap.getValue(PREFIX_SEX).get())); + } + if (argMultimap.getValue(PREFIX_STATUS).isPresent()) { + updatePersonDescriptor.setStatus(ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).get())); + } + + // Data fields + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + updatePersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_COUNTRY).isPresent()) { + updatePersonDescriptor.setCountry(ParserUtil.parseCountry(argMultimap.getValue(PREFIX_COUNTRY).get())); + } + + // Medical information + if (argMultimap.getValue(PREFIX_ALLERGIES).isPresent()) { + updatePersonDescriptor.setAllergies( + ParserUtil.parseAllergies(argMultimap.getValue(PREFIX_ALLERGIES).get())); + } + if (argMultimap.getValue(PREFIX_BLOODTYPE).isPresent()) { + updatePersonDescriptor.setBloodType( + ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE).get())); + } + if (argMultimap.getValue(PREFIX_CONDITION).isPresent()) { + updatePersonDescriptor.setCondition( + ParserUtil.parseCondition(argMultimap.getValue(PREFIX_CONDITION).get())); + } + if (argMultimap.getValue(PREFIX_DATEOFADMISSION).isPresent()) { + updatePersonDescriptor.setDateOfAdmission( + ParserUtil.parseDateOfAdmission(argMultimap.getValue(PREFIX_DATEOFADMISSION).get())); + } + if (argMultimap.getValue(PREFIX_DIAGNOSIS).isPresent()) { + updatePersonDescriptor.setDiagnosis( + ParserUtil.parseDiagnosis(argMultimap.getValue(PREFIX_DIAGNOSIS).get())); + } + if (argMultimap.getValue(PREFIX_SYMPTOM).isPresent()) { + updatePersonDescriptor.setSymptom(ParserUtil.parseSymptom(argMultimap.getValue(PREFIX_SYMPTOM).get())); + } + + + if (!updatePersonDescriptor.isAnyFieldUpdated()) { + throw new ParseException(UpdateCommand.MESSAGE_NOT_UPDATED); + } + + return new UpdateCommand(nric, updatePersonDescriptor); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/ImmuniMate.java similarity index 59% rename from src/main/java/seedu/address/model/AddressBook.java rename to src/main/java/seedu/address/model/ImmuniMate.java index 73397161e84..95da02f1cd6 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/ImmuniMate.java @@ -3,19 +3,24 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Objects; import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.visit.UniqueVisitList; +import seedu.address.model.visit.Visit; /** * Wraps all data at the address-book level * Duplicates are not allowed (by .isSamePerson comparison) */ -public class AddressBook implements ReadOnlyAddressBook { +public class ImmuniMate implements ReadOnlyImmuniMate { + //TODO test cases private final UniquePersonList persons; + private final UniqueVisitList visits; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,14 +31,15 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + visits = new UniqueVisitList(); } - public AddressBook() {} + public ImmuniMate() {} /** * Creates an AddressBook using the Persons in the {@code toBeCopied} */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { + public ImmuniMate(ReadOnlyImmuniMate toBeCopied) { this(); resetData(toBeCopied); } @@ -48,13 +54,24 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + + /** + * Replaces the contents of the visit list with {@code visits}. + * {@code visits} must not contain duplicate visits. + */ + public void setVisits(List visits) { + this.visits.setVisits(visits); + } + + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ - public void resetData(ReadOnlyAddressBook newData) { + public void resetData(ReadOnlyImmuniMate newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setVisits(newData.getVisitList()); } //// person-level operations @@ -67,6 +84,14 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + public boolean hasVisit(Visit visit) { + requireNonNull(visit); + return visits.contains(visit); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -75,6 +100,14 @@ public void addPerson(Person p) { persons.add(p); } + /** + * Adds a visit to the address book. + * The visit must not already exist in the address book. + */ + public void addVisit(Visit v) { + visits.add(v); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -86,6 +119,18 @@ public void setPerson(Person target, Person editedPerson) { persons.setPerson(target, editedPerson); } + /** + * Replaces the given visit {@code target} in the list with {@code editedVisit}. + * {@code target} must exist in the address book. + * The unique identifier of {@code editedVisit} must not be the same as another existing visit in the address book. + */ + public void setVisit(Visit target, Visit editedVisit) { + requireNonNull(editedVisit); + + visits.setVisit(target, editedVisit); + } + + /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. @@ -94,12 +139,22 @@ public void removePerson(Person key) { persons.remove(key); } + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeVisit(Visit key) { + visits.remove(key); + } + + //// util methods @Override public String toString() { return new ToStringBuilder(this) .add("persons", persons) + .add("visits", visits) .toString(); } @@ -108,6 +163,11 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getVisitList() { + return visits.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -115,16 +175,16 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof AddressBook)) { + if (!(other instanceof ImmuniMate)) { return false; } - AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); + ImmuniMate otherAddressBook = (ImmuniMate) other; + return persons.equals(otherAddressBook.persons) && visits.equals(otherAddressBook.visits); } @Override public int hashCode() { - return persons.hashCode(); + return Objects.hash(persons, visits); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..b2d3b2226e0 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -6,6 +6,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; /** * The API of the Model component. @@ -14,6 +15,9 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_VISITS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -37,38 +41,56 @@ public interface Model { /** * Returns the user prefs' address book file path. */ - Path getAddressBookFilePath(); + Path getImmunimateFilePath(); /** * Sets the user prefs' address book file path. */ - void setAddressBookFilePath(Path addressBookFilePath); + void setImmunimateFilePath(Path immuniMateFilePath); /** * Replaces address book data with the data in {@code addressBook}. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + void setImmuniMate(ReadOnlyImmuniMate immuniMate); /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyImmuniMate getImmuniMate(); /** * 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 visit with same unique identifier as {@code visit} exists in the address book. + */ + boolean hasVisit(Visit visit); + /** * Deletes the given person. * The person must exist in the address book. */ void deletePerson(Person target); + /** + * Deletes the given visit. + * The visit must exist in the address book. + */ + void deleteVisit(Visit target); + + /** * Adds the given person. * {@code person} must not already exist in the address book. */ void addPerson(Person person); + /** + * Adds the given visit. + * {@code visit} must not already exist in the address book. + */ + void addVisit(Visit visit); + /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. @@ -76,12 +98,28 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + /** + * Replaces the given visit {@code target} with {@code editedVisit}. + * {@code target} must exist in the address book. + * The unique identifier of {@code editedVisit} must not be the same as another existing visit in the address book. + */ + void setVisit(Visit target, Visit editedVisit); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered visit list */ + ObservableList getFilteredVisitList(); + /** * 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); + + /** + * Updates the filter of the filtered visit list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredVisitList(Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..fe32ee5bc67 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,6 +12,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; /** * Represents the in-memory model of the address book data. @@ -19,25 +20,28 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final ImmuniMate immuniMate; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredVisits; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given system and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyImmuniMate immuniMate, ReadOnlyUserPrefs userPrefs) { + requireAllNonNull(immuniMate, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with Immunimate: " + immuniMate + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.immuniMate = new ImmuniMate(immuniMate); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredVisits = new FilteredList<>(this.immuniMate.getVisitList()); + filteredPersons = new FilteredList<>(this.immuniMate.getPersonList()); + } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new ImmuniMate(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -65,50 +69,74 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { + public Path getImmunimateFilePath() { return userPrefs.getAddressBookFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public void setImmunimateFilePath(Path immuniMateFilePath) { + requireNonNull(immuniMateFilePath); + userPrefs.setAddressBookFilePath(immuniMateFilePath); } //=========== AddressBook ================================================================================ @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public void setImmuniMate(ReadOnlyImmuniMate immuniMate) { + this.immuniMate.resetData(immuniMate); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public ReadOnlyImmuniMate getImmuniMate() { + return immuniMate; } + //TODO test cases @Override public boolean hasPerson(Person person) { requireNonNull(person); - return addressBook.hasPerson(person); + return immuniMate.hasPerson(person); + } + + @Override + public boolean hasVisit(Visit visit) { + requireNonNull(visit); + return immuniMate.hasVisit(visit); } + //TODO test cases @Override public void deletePerson(Person target) { - addressBook.removePerson(target); + immuniMate.removePerson(target); + } + + @Override + public void deleteVisit(Visit target) { + immuniMate.removeVisit(target); } @Override public void addPerson(Person person) { - addressBook.addPerson(person); + immuniMate.addPerson(person); updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } + @Override + public void addVisit(Visit visit) { + immuniMate.addVisit(visit); + updateFilteredVisitList(PREDICATE_SHOW_ALL_VISITS); + } + @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); + immuniMate.setPerson(target, editedPerson); + } + + @Override + public void setVisit(Visit target, Visit editedVisit) { + } //=========== Filtered Person List Accessors ============================================================= @@ -122,12 +150,23 @@ public ObservableList getFilteredPersonList() { return filteredPersons; } + @Override + public ObservableList getFilteredVisitList() { + return filteredVisits; + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } + @Override + public void updateFilteredVisitList(Predicate predicate) { + requireNonNull(predicate); + filteredVisits.setPredicate(predicate); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -140,7 +179,7 @@ public boolean equals(Object other) { } ModelManager otherModelManager = (ModelManager) other; - return addressBook.equals(otherModelManager.addressBook) + return immuniMate.equals(otherModelManager.immuniMate) && userPrefs.equals(otherModelManager.userPrefs) && filteredPersons.equals(otherModelManager.filteredPersons); } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyImmuniMate.java similarity index 64% rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java rename to src/main/java/seedu/address/model/ReadOnlyImmuniMate.java index 6ddc2cd9a29..68ef1050f06 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyImmuniMate.java @@ -2,11 +2,12 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; /** * Unmodifiable view of an address book */ -public interface ReadOnlyAddressBook { +public interface ReadOnlyImmuniMate { /** * Returns an unmodifiable view of the persons list. @@ -14,4 +15,8 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the visits + */ + ObservableList getVisitList(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..7813c68a5cf 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "immunimate.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..61fc5493e91 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -17,7 +17,7 @@ public class Address { */ public static final String VALIDATION_REGEX = "[^\\s].*"; - public final String value; + private final String value; /** * Constructs an {@code Address}. diff --git a/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..0a2340088be --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public AddressContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPhraseIgnoreCase(person.getAddress().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddressContainsKeywordsPredicate)) { + return false; + } + + AddressContainsKeywordsPredicate otherAddressContainsKeywordsPredicate = + (AddressContainsKeywordsPredicate) other; + return keywords.equals(otherAddressContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/AddressDiagnosisStatusPredicate.java b/src/main/java/seedu/address/model/person/AddressDiagnosisStatusPredicate.java new file mode 100644 index 00000000000..3ecb6aac0ce --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressDiagnosisStatusPredicate.java @@ -0,0 +1,72 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Status} matches any of the keywords given. + */ +public class AddressDiagnosisStatusPredicate implements Predicate { + private final String address; + private final String disease; + private final String status; + + /** + * Constructor for AddressDiagnosisStatusPredicate object + * + * @param address + * @param disease + * @param status + */ + public AddressDiagnosisStatusPredicate(String address, String disease, String status) { + this.address = address; + this.disease = disease; + this.status = status; + } + + public String getAddress() { + return address; + } + + public String getDisease() { + return disease; + } + + @Override + public boolean test(Person person) { + if (person.getDiagnosis() == null) { + return false; + } + return person.getAddress().toString().toLowerCase().contains(address.toLowerCase()) + && person.getDiagnosis().toString().toLowerCase().contains(disease.toLowerCase()) + && person.getStatus().toString().equals(status); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddressDiagnosisStatusPredicate)) { + return false; + } + + AddressDiagnosisStatusPredicate otherAddressAndStatusPredicate = + (AddressDiagnosisStatusPredicate) other; + return address.equals(otherAddressAndStatusPredicate.address) + && disease.equals(otherAddressAndStatusPredicate.disease) + && status.equals(otherAddressAndStatusPredicate.status); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("address", address) + .add("disease", disease) + .add("status", status) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Allergies.java b/src/main/java/seedu/address/model/person/Allergies.java new file mode 100644 index 00000000000..54045dadb8f --- /dev/null +++ b/src/main/java/seedu/address/model/person/Allergies.java @@ -0,0 +1,66 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's allergies in the address book. + * Guarantees: immutable; + */ +public class Allergies { + //making string non-empty because empty input is already represented by null, + //and if allowed, can cause storage problems + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String MESSAGE_CONSTRAINTS = "Allergies can take any values, and it should not be blank"; + private final String allergies; + /** + * Constructs an {@code Allergies}. + * + * @param allergies A valid allergies. + */ + public Allergies(String allergies) { + requireNonNull(allergies); + checkArgument(isValidAllergies(allergies), MESSAGE_CONSTRAINTS); + this.allergies = allergies; + } + public static boolean isValidAllergies(String test) { + return test.matches(VALIDATION_REGEX); + } + public String getAllergies() { + return allergies; + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return allergies == null ? alt : allergies; + } + + @Override + public String toString() { + return this.allergies; + } + + @Override + public int hashCode() { + return allergies.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Allergies)) { + return false; + } + + Allergies otherAllergies = (Allergies) other; + return allergies.equals(otherAllergies.allergies); + } +} diff --git a/src/main/java/seedu/address/model/person/BloodType.java b/src/main/java/seedu/address/model/person/BloodType.java new file mode 100644 index 00000000000..7a8b478926c --- /dev/null +++ b/src/main/java/seedu/address/model/person/BloodType.java @@ -0,0 +1,69 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's blood type in the address book. + * Guarantees: immutable; + */ +public class BloodType { + public static final String MESSAGE_CONSTRAINTS = "BloodType should be either A+, A-, B+, B-, AB+, AB-, O+, O-"; + public static final String VALIDATION_REGEX = "^(A\\+|A-|B\\+|B-|AB\\+|AB-|O\\+|O-)$"; + + private final String bloodType; + + /** + * Constructs a {@code BloodType}. + * + * @param bloodType A valid blood type in String format + */ + public BloodType(String bloodType) { + requireNonNull(bloodType); + checkArgument(isValidBloodType(bloodType), MESSAGE_CONSTRAINTS); + this.bloodType = bloodType; + } + + /** + * A method for testing if string forms a valid blood group + * + * @param test String for storing testing type + * @return Boolean whether test passes or fails + */ + public static boolean isValidBloodType(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns given placeholder string if value field is not initialised + * + * @param alt An alternate return value + * @return placeholder string + */ + public String orElse(String alt) { + return this.bloodType == null ? alt : this.toString(); + } + + @Override + public String toString() { + return this.bloodType; + } + + @Override + public int hashCode() { + return this.bloodType.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof BloodType)) { + return false; + } + BloodType otherBloodType = (BloodType) other; + return this.bloodType.equals(otherBloodType.bloodType); + } +} diff --git a/src/main/java/seedu/address/model/person/Condition.java b/src/main/java/seedu/address/model/person/Condition.java new file mode 100644 index 00000000000..7b299b0306d --- /dev/null +++ b/src/main/java/seedu/address/model/person/Condition.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's condition in the address book. + * Guarantees: immutable; + */ +public class Condition { + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String MESSAGE_CONSTRAINTS = "Conditions can take any values, and it should not be blank"; + private final String condition; + + /** + * Constructs an {@code Condition}. + * + * @param condition A valid condition. + */ + public Condition(String condition) { + requireNonNull(condition); + checkArgument(isValidCondition(condition), MESSAGE_CONSTRAINTS); + this.condition = condition; + } + /** + * Returns true if a given string is a valid condition. + */ + public static boolean isValidCondition(String test) { + return test.matches(VALIDATION_REGEX); + } + + + public String getCondition() { + return condition; + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return condition == null ? alt : condition; + } + + @Override + public String toString() { + return this.condition; + } + + @Override + public int hashCode() { + return condition.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Condition)) { + return false; + } + + Condition otherCondition = (Condition) other; + return condition.equals(otherCondition.condition); + } +} diff --git a/src/main/java/seedu/address/model/person/ConditionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/ConditionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..c63cd627f19 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ConditionContainsKeywordsPredicate.java @@ -0,0 +1,48 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Condition} matches any of the keywords given. + */ +public class ConditionContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public ConditionContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (person.getCondition() == null) { + return false; + } + return keywords.stream().anyMatch( + keyword -> StringUtil.containsPhraseIgnoreCase(person.getCondition().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ConditionContainsKeywordsPredicate)) { + return false; + } + + ConditionContainsKeywordsPredicate otherConditionContainsKeywordsPredicate = + (ConditionContainsKeywordsPredicate) other; + return keywords.equals(otherConditionContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Country.java b/src/main/java/seedu/address/model/person/Country.java new file mode 100644 index 00000000000..e2a06dfc3b3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Country.java @@ -0,0 +1,64 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's condition in the address book. + * Guarantees: immutable; + */ +public class Country { + //making string non-empty because empty input is already represented by null, + //and if allowed, can cause storage problems + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String MESSAGE_CONSTRAINTS = "Countries can take any values, and it should not be blank." + + " It is case insensitive."; + private final String country; + + /** + * Constructs an {@code Country}. + * + * @param country A valid country. + */ + + public Country(String country) { + requireNonNull(country); + checkArgument(isValidCountry(country), MESSAGE_CONSTRAINTS); + this.country = country; + } + + public static boolean isValidCountry(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return country == null ? alt : country; + } + + @Override + public String toString() { + return this.country; + } + + @Override + public int hashCode() { + return country.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Country)) { + return false; + } + Country otherCountry = (Country) other; + return country.equals(otherCountry.country); + } +} diff --git a/src/main/java/seedu/address/model/person/DateOfAdmission.java b/src/main/java/seedu/address/model/person/DateOfAdmission.java new file mode 100644 index 00000000000..0ba0bc32cc4 --- /dev/null +++ b/src/main/java/seedu/address/model/person/DateOfAdmission.java @@ -0,0 +1,78 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; + + +/** + * Represents a Person's date of admission in the address book. + * Guarantees: immutable; + */ +public class DateOfAdmission { + //Changed validity check for date format to disallow invalid dates, as not doing so results in parser errors + public static final String MESSAGE_CONSTRAINTS = + "Date of admission should be in the format of YYYY-MM-DD. It should be a valid date," + + " and it should not be blank."; + + private final LocalDate dateOfAdmission; + + /** + * Constructs a {@code DateOfAdmission}. + * + * @param dateOfAdmission A valid date of admission. + */ + public DateOfAdmission(String dateOfAdmission) { + requireNonNull(dateOfAdmission); + if (!isValidDateOfAdmission(dateOfAdmission)) { + throw new IllegalArgumentException(MESSAGE_CONSTRAINTS); + } + this.dateOfAdmission = LocalDate.parse(dateOfAdmission); + } + + /** + * Returns true if a given string is a valid date of admission. + */ + public static boolean isValidDateOfAdmission(String test) { + try { + LocalDate.parse(test); + } catch (Exception e) { + return false; + } + return true; + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return dateOfAdmission == null ? alt : dateOfAdmission.toString(); + } + + @Override + public String toString() { + return this.dateOfAdmission.toString(); + } + + @Override + public int hashCode() { + return dateOfAdmission.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DateOfAdmission)) { + return false; + } + + DateOfAdmission otherDateOfAdmission = (DateOfAdmission) other; + return dateOfAdmission.equals(otherDateOfAdmission.dateOfAdmission); + } +} diff --git a/src/main/java/seedu/address/model/person/DateOfBirth.java b/src/main/java/seedu/address/model/person/DateOfBirth.java new file mode 100644 index 00000000000..89b7cd922d7 --- /dev/null +++ b/src/main/java/seedu/address/model/person/DateOfBirth.java @@ -0,0 +1,69 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; + + +/** + * Represents a Person's date of admission in the address book. + * Guarantees: immutable; + */ +public class DateOfBirth { + //Changed validity check for date format to disallow invalid dates, as not doing so results in parser error + public static final String MESSAGE_CONSTRAINTS = + "Date of birth should be in the format of YYYY-MM-DD. It should be a valid date," + + " and it should not be blank."; + + private final LocalDate dateOfBirth; + + /** + * Constructs a {@code DateOfBirth}. + * + * @param dateOfBirth A valid date of birth. + */ + public DateOfBirth(String dateOfBirth) { + requireNonNull(dateOfBirth); + if (!isValidDateOfBirth(dateOfBirth)) { + throw new IllegalArgumentException(MESSAGE_CONSTRAINTS); + } + this.dateOfBirth = LocalDate.parse(dateOfBirth); + } + + /** + * Returns true if a given string is a valid date of birth. + */ + public static boolean isValidDateOfBirth(String test) { + try { + LocalDate.parse(test); + } catch (Exception e) { + return false; + } + return true; + } + + @Override + public String toString() { + return this.dateOfBirth.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DateOfBirth)) { + return false; + } + + DateOfBirth otherDateOfBirth = (DateOfBirth) other; + return dateOfBirth.equals(otherDateOfBirth.dateOfBirth); + } + + @Override + public int hashCode() { + return dateOfBirth.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Diagnosis.java b/src/main/java/seedu/address/model/person/Diagnosis.java new file mode 100644 index 00000000000..b2d5ab1d314 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Diagnosis.java @@ -0,0 +1,68 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's diagnosis in the address book. + * Guarantees: immutable; + */ +public class Diagnosis { + //making string non-empty because empty input is already represented by null, + //and if allowed, can cause storage problems + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String MESSAGE_CONSTRAINTS = "Diagnosis can take any values, and it should not be blank"; + private final String diagnosis; + + /** + * Constructs a {@code Diagnosis}. + * + * @param diagnosis A valid diagnosis. + */ + public Diagnosis(String diagnosis) { + requireNonNull(diagnosis); + checkArgument(isValidDiagnosis(diagnosis), MESSAGE_CONSTRAINTS); + this.diagnosis = diagnosis; + } + public static boolean isValidDiagnosis(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getDiagnosis() { + return diagnosis; + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return diagnosis == null ? alt : diagnosis; + } + + @Override + public String toString() { + return this.diagnosis; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Diagnosis)) { + return false; + } + + Diagnosis otherDiagnosis = (Diagnosis) other; + return diagnosis.equals(otherDiagnosis.diagnosis); + } + + @Override + public int hashCode() { + return diagnosis.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..fe85fb260e4 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -20,7 +20,7 @@ public class Email { + "The domain name must:\n" + " - end with a domain label at least 2 characters long\n" + " - have each domain label start and end with alphanumeric characters\n" - + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; + + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; // alphanumeric and special characters private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" @@ -31,7 +31,7 @@ public class Email { private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; - public final String value; + private final String value; /** * Constructs an {@code Email}. @@ -51,6 +51,15 @@ public static boolean isValidEmail(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return value == null ? alt : value; + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 173f15b9b00..bd4c4055d1b 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -18,7 +18,7 @@ public class Name { */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - public final String fullName; + private final String fullName; /** * Constructs a {@code Name}. diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..1cd445af15c 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().toString(), keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Nric.java b/src/main/java/seedu/address/model/person/Nric.java new file mode 100644 index 00000000000..09387b24c76 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Nric.java @@ -0,0 +1,68 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's NRIC in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidNric(String)} + */ +public class Nric { + public static final String MESSAGE_CONSTRAINTS = + "NRIC number should contain a prefix of S or T, followed by 7 digits, and end with a letter. " + + "There should not be blanks."; + + /** + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "^[ST]\\d{7}[A-Z]$"; + + private final String nric; + + /** + * Constructs a {@code NRIC}. + * + * @param nric A valid NRIC. + */ + public Nric(String nric) { + requireNonNull(nric); + checkArgument(isValidNric(nric), MESSAGE_CONSTRAINTS); + this.nric = nric.toUpperCase(); + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidNric(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return this.nric; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Nric)) { + return false; + } + + Nric otherNric = (Nric) other; + return nric.equals(otherNric.nric); + } + + @Override + public int hashCode() { + return nric.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/NricContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NricContainsKeywordsPredicate.java new file mode 100644 index 00000000000..0c2f5fced9d --- /dev/null +++ b/src/main/java/seedu/address/model/person/NricContainsKeywordsPredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Nric} matches any of the keywords given. + */ +public class NricContainsKeywordsPredicate implements Predicate { + private final String keywords; + + public NricContainsKeywordsPredicate(String keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return StringUtil.containsWordIgnoreCase(person.getNric().toString(), keywords); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NricContainsKeywordsPredicate)) { + return false; + } + + NricContainsKeywordsPredicate otherNricContainsKeywordsPredicate = (NricContainsKeywordsPredicate) other; + return keywords.equals(otherNricContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..98fbbd51041 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,39 +2,69 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -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. */ public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - + //Mandatory fields + //Identity fields + private final Nric nric; + private Name name; + private Phone phone; + private Address address; + private final DateOfBirth dateOfBirth; + private Sex sex; + private Status status; + // Optional fields // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - + private Email email = null; + private Country country = null; + //Medical information + private Allergies allergies = null; + private BloodType bloodType = null; + private Condition condition = null; + private DateOfAdmission dateOfAdmission = null; + private Diagnosis diagnosis = null; + private Symptom symptom = null; /** - * Every field must be present and not null. + * Every mandatory field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Nric nric, Name name, Phone phone, Address address, DateOfBirth dateOfBirth, Sex sex, Status status) { + //Only the fields that are mandatory are included down here + requireAllNonNull(nric, name, phone, address, dateOfBirth, sex); + this.nric = nric; this.name = name; this.phone = phone; - this.email = email; this.address = address; - this.tags.addAll(tags); + this.dateOfBirth = dateOfBirth; + this.sex = sex; + this.status = status; + } + /** + * Constructor for Person with only Nric. + */ + private Person(Nric nric) { + this.nric = nric; + this.name = null; + this.phone = null; + this.address = null; + this.dateOfBirth = null; + this.sex = null; + this.status = null; + } + /** + * Constructor for Person with only Nric. + */ + public static Person createPersonWithNric(Nric nric) { + return new Person(nric); + } + public Nric getNric() { + return nric; } public Name getName() { @@ -49,20 +79,143 @@ public Email getEmail() { return email; } + public Sex getSex() { + return this.sex; + } + public Address getAddress() { return address; } + public Allergies getAllergies() { + return allergies; + } + + public BloodType getBloodType() { + return bloodType; + } + + public Country getCountry() { + return country; + } + + public DateOfBirth getDateOfBirth() { + return dateOfBirth; + } + + public Condition getCondition() { + return condition; + } + + public DateOfAdmission getDateOfAdmission() { + return dateOfAdmission; + } + + public Diagnosis getDiagnosis() { + return diagnosis; + } + + public Status getStatus() { + return this.status; + } + + public Symptom getSymptom() { + return symptom; + } + + /** + * Sets the name of the person to input value. + * @param name the new name. + */ + public void setName(Name name) { + this.name = name; + } + + /** + * Sets the phone of the person to input value. + * @param phone the new phone. + */ + public void setPhone(Phone phone) { + this.phone = phone; + } + /** + * Sets the address of the person to input value. + */ + public void setAddress(Address address) { + this.address = address; + } /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. + * Sets the sex of the person to input value. */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public void setSex(Sex sex) { + this.sex = sex; + } + /** + * Sets the status of the person to input value. + * @param status the new status. + */ + public void setStatus(Status status) { + this.status = status; + } + /** + * Sets the email of the person to input value. + * @param email the new email. + */ + public void setEmail(Email email) { + this.email = email; + } + /** + * Sets the allergies of the person to input value. + * @param allergies the new allergies. + */ + public void setAllergies(Allergies allergies) { + this.allergies = allergies; + } + /** + * Sets the blood type of the person to input value. + * @param bloodType the new blood type. + */ + public void setBloodType(BloodType bloodType) { + this.bloodType = bloodType; + } + /** + * Sets the country of the person to input value. + * @param country the new country. + */ + public void setCountry(Country country) { + this.country = country; + } + /** + * Sets the condition of the person to input value. + * @param condition the new condition. + */ + public void setCondition(Condition condition) { + this.condition = condition; + } + /** + * Sets the date of admission of the person to input value. + * @param dateOfAdmission the new date of admission. + */ + public void setDateOfAdmission(DateOfAdmission dateOfAdmission) { + this.dateOfAdmission = dateOfAdmission; + } + /** + * Sets the diagnosis of the person to input value. + * @param diagnosis the new diagnosis. + */ + public void setDiagnosis(Diagnosis diagnosis) { + this.diagnosis = diagnosis; + } + /** + * Sets the symptom of the person to input value. + * @param symptom the new symptom. + */ + public void setSymptom(Symptom symptom) { + this.symptom = symptom; } /** - * Returns true if both persons have the same name. + * Returns true if both persons have the same nric. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { @@ -70,12 +223,24 @@ public boolean isSamePerson(Person otherPerson) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + return otherPerson != null && otherPerson.getNric().equals(getNric()); } /** - * Returns true if both persons have the same identity and data fields. + * Returns true if the person has all mandatory fields. + */ + public static boolean isValidPerson(Person person) { + return person.nric != null + && person.name != null + && person.phone != null + && person.address != null + && person.dateOfBirth != null + && person.sex != null + && person.status != null; + } + + /** + * Returns true if both persons have the same identity and all data fields. * This defines a stronger notion of equality between two persons. */ @Override @@ -88,30 +253,64 @@ public boolean equals(Object other) { if (!(other instanceof Person)) { return false; } - Person otherPerson = (Person) other; - return name.equals(otherPerson.name) + if (!(isValidPerson(this) && isValidPerson(otherPerson))) { + return false; + } + return nric.equals(otherPerson.nric) + && name.equals(otherPerson.name) && phone.equals(otherPerson.phone) - && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && dateOfBirth.equals(otherPerson.dateOfBirth) + && sex.equals(otherPerson.sex) + && status.equals(otherPerson.status) + && Objects.equals(email, otherPerson.email) + && Objects.equals(country, otherPerson.country) + && Objects.equals(allergies, otherPerson.allergies) + && Objects.equals(bloodType, otherPerson.bloodType) + && Objects.equals(condition, otherPerson.condition) + && Objects.equals(dateOfAdmission, otherPerson.dateOfAdmission) + && Objects.equals(diagnosis, otherPerson.diagnosis) + && Objects.equals(symptom, otherPerson.symptom); } @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(nric, name, phone, address, dateOfBirth, sex, status, email, country, allergies, bloodType, + condition, dateOfAdmission, diagnosis, symptom); } @Override public String toString() { + // list view return new ToStringBuilder(this) + .add("nric", nric) + .add("name", name) + .add("status", status).toString(); + } + + /** + * @return Detailed String of Person + */ + public String toDetailedString() { + // detailed view + return new ToStringBuilder(this) + .add("nric", nric) .add("name", name) .add("phone", phone) - .add("email", email) .add("address", address) - .add("tags", tags) + .add("birthday", dateOfBirth) + .add("sex", sex) + .add("status", status) + .add("email", email) + .add("admission", dateOfAdmission) + .add("allergies", allergies) + .add("blood type", bloodType) + .add("country", country) + .add("condition", condition) + .add("diagnosis", diagnosis) + .add("symptom", symptom) .toString(); } - } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..1f71c89c46a 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,9 +11,10 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; + "Phone numbers should only contain numbers, and it should be at least 8 digits long"; + //TODO: change REGEX to give correct results + public static final String VALIDATION_REGEX = "\\d{8}"; + private final String value; /** * Constructs a {@code Phone}. diff --git a/src/main/java/seedu/address/model/person/Sex.java b/src/main/java/seedu/address/model/person/Sex.java new file mode 100644 index 00000000000..800b11bae1a --- /dev/null +++ b/src/main/java/seedu/address/model/person/Sex.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Person's status in the address book. + * Guarantees: immutable; + */ +public class Sex { + public static final String MESSAGE_CONSTRAINTS = "Sex should be either F or M."; + + /** + * Represents sex of a person. + */ + private enum SexType { F, M } + private final SexType sex; + + /** + * Constructs a Sex instance. + */ + public Sex(String sex) { + requireNonNull(sex); + checkArgument(isValidSex(sex), MESSAGE_CONSTRAINTS); + this.sex = SexType.valueOf(sex); + } + + /** + * Checks if a String matches the Enum + * @param testString String of input + * @return Boolean + */ + public static boolean isValidSex(String testString) { + try { + SexType sexType = SexType.valueOf(testString); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public String toString() { + return this.sex.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Sex)) { + return false; + } + + Sex otherSex = (Sex) other; + return sex.equals(otherSex.sex); + } + + @Override + public int hashCode() { + return sex.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Status.java b/src/main/java/seedu/address/model/person/Status.java new file mode 100644 index 00000000000..3e5e356995e --- /dev/null +++ b/src/main/java/seedu/address/model/person/Status.java @@ -0,0 +1,79 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's status in the address book. + * Guarantees: immutable; + */ +public class Status { + + public static final String MESSAGE_CONSTRAINTS = + "Status should only be one of the following: HEALTHY, UNWELL, PENDING."; + + /** + * Represents status of a person. + */ + public enum StatusType { HEALTHY, UNWELL, PENDING } + private final StatusType status; + + /** + * Constructs a Status instance. + * + * @param status Status of a person + */ + public Status(String status) { + requireNonNull(status); + checkArgument(isValidStatus(status), MESSAGE_CONSTRAINTS); + this.status = StatusType.valueOf(status); + } + + /** + * Gets status type + * + * @return Status type of this status + */ + public StatusType getStatusType() { + return this.status; + } + + /** + * Checks if a String matches the Enum + * @param testString String of input + * @return Boolean + */ + public static boolean isValidStatus(String testString) { + try { + StatusType statusType = StatusType.valueOf(testString); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + @Override + public String toString() { + // TODO Implement Custom toString format + return this.status.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Status)) { + return false; + } + + Status otherSex = (Status) other; + return status.equals(otherSex.status); + } + + @Override + public int hashCode() { + return status.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Symptom.java b/src/main/java/seedu/address/model/person/Symptom.java new file mode 100644 index 00000000000..d194edf73bd --- /dev/null +++ b/src/main/java/seedu/address/model/person/Symptom.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's symptom in the address book. + * Guarantees: immutable; + */ +public class Symptom { + //making string non-empty because empty input is already represented by null, + //and if allowed, can cause storage problems + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String MESSAGE_CONSTRAINTS = "Symptom can take any values, and it should not be blank"; + private final String symptom; + + /** + * Constructs a {@code Symptom}. + * + * @param symptom A valid symptom. + */ + public Symptom(String symptom) { + requireNonNull(symptom); + checkArgument(isValidSymptom(symptom), MESSAGE_CONSTRAINTS); + this.symptom = symptom; + } + public static boolean isValidSymptom(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getSymptom() { + return symptom; + } + + /** + * Returns given placeholder string if value field is not initialised + * @param alt + * @return placeholder string + */ + public String orElse(String alt) { + return symptom == null ? alt : symptom; + } + + @Override + public String toString() { + return this.symptom; + } + + @Override + public int hashCode() { + return symptom.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Symptom)) { + return false; + } + + Symptom otherSymptom = (Symptom) other; + return symptom.equals(otherSymptom.symptom); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..d0045f24964 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -31,11 +31,20 @@ public class UniquePersonList implements Iterable { /** * Returns true if the list contains an equivalent person as the given argument. */ + public boolean contains(Person toCheck) { requireNonNull(toCheck); return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains a person with the given NRIC. + */ + public boolean containsNric(Nric nric) { + requireNonNull(nric); + return internalList.stream().anyMatch(person -> person.getNric().equals(nric)); + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -148,3 +157,4 @@ private boolean personsAreUnique(List persons) { return true; } } + diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..965a8659b9f 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,17 +1,19 @@ package seedu.address.model.util; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ImmuniMate; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.DateOfBirth; +import seedu.address.model.person.Diagnosis; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Sex; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; +import seedu.address.model.visit.DateOfVisit; +import seedu.address.model.visit.Visit; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -19,42 +21,44 @@ 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 Person(new Nric("T0234567C"), new Name("Alex Yeoh"), new Phone("87438807"), + new Address("Blk 30 Geylang Street 29, #06-40"), new DateOfBirth("1977-04-03"), + new Sex("M"), new Status("HEALTHY")), + new Person(new Nric("S9234568N"), new Name("Bernice Yu"), new Phone("99272758"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new DateOfBirth("1977-04-03"), + new Sex("F"), new Status("UNWELL")), + new Person(new Nric("S8934569Z"), new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new DateOfBirth("2001-04-03"), + new Sex("F"), new Status("HEALTHY")), + new Person(new Nric("T0134560A"), new Name("David Li"), new Phone("91031282"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new DateOfBirth("1987-11-03"), + new Sex("M"), new Status("PENDING")), + new Person(new Nric("T0534161B"), new Name("Irfan Ibrahim"), new Phone("92492021"), + new Address("Blk 47 Tampines Street 20, #17-35"), new DateOfBirth("1970-12-03"), + new Sex("M"), new Status("UNWELL")), + new Person(new Nric("S9781662B"), new Name("Roy Balakrishnan"), new Phone("92624417"), + new Address("Blk 45 Aljunied Street 85, #11-31"), new DateOfBirth("1987-04-03"), + new Sex("M"), new Status("PENDING")) + }; + } + + public static Visit[] getSampleVisits() { + return new Visit[] { + new Visit(new Nric("T0234567C"), new DateOfVisit("2023-01-02"), + new Symptom("Dying"), new Diagnosis("Cancer"), new Status("UNWELL")), + new Visit(new Nric("T0234567C"), new DateOfVisit("2023-02-25"), + new Symptom("Throat Pain"), new Diagnosis("Cancer"), new Status("HEALTHY")), + new Visit(new Nric("S9234568N"), new DateOfVisit("2023-01-02"), + new Symptom("Headache"), new Diagnosis("COVID"), new Status("UNWELL")), }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); + public static ReadOnlyImmuniMate getSampleAddressBook() { + ImmuniMate sampleAb = new ImmuniMate(); for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } return sampleAb; } - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - } diff --git a/src/main/java/seedu/address/model/visit/DateOfVisit.java b/src/main/java/seedu/address/model/visit/DateOfVisit.java new file mode 100644 index 00000000000..a024720f28f --- /dev/null +++ b/src/main/java/seedu/address/model/visit/DateOfVisit.java @@ -0,0 +1,68 @@ +package seedu.address.model.visit; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; + +/** + * Represents a Person's date of admission in the address book. + * Guarantees: immutable; + */ +public class DateOfVisit { + //Changed validity check for date format to disallow invalid dates, as not doing so results in parser error + public static final String MESSAGE_CONSTRAINTS = + "Date of visit should be in the format of YYYY-MM-DD. It should be a valid date," + + " and it should not be blank."; + + private final LocalDate dateOfVisit; + + /** + * Constructs a {@code DateOfBirth}. + * + * @param dateOfBirth A valid date of birth. + */ + public DateOfVisit(String dateOfBirth) { + requireNonNull(dateOfBirth); + if (!isValidDateOfVisit(dateOfBirth)) { + throw new IllegalArgumentException(MESSAGE_CONSTRAINTS); + } + this.dateOfVisit = LocalDate.parse(dateOfBirth); + } + + /** + * Returns true if a given string is a valid date of birth. + */ + public static boolean isValidDateOfVisit(String test) { + try { + LocalDate.parse(test); + } catch (Exception e) { + return false; + } + return true; + } + + @Override + public String toString() { + return this.dateOfVisit.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DateOfVisit)) { + return false; + } + + DateOfVisit otherDateOfBirth = (DateOfVisit) other; + return dateOfVisit.equals(otherDateOfBirth.dateOfVisit); + } + + @Override + public int hashCode() { + return dateOfVisit.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/visit/UniqueVisitList.java b/src/main/java/seedu/address/model/visit/UniqueVisitList.java new file mode 100644 index 00000000000..a4883826c7a --- /dev/null +++ b/src/main/java/seedu/address/model/visit/UniqueVisitList.java @@ -0,0 +1,148 @@ +package seedu.address.model.visit; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.visit.exceptions.DuplicateVisitException; +import seedu.address.model.visit.exceptions.VisitNotFoundException; + +/** + * A list of visits that enforces uniqueness between its elements and does not allow nulls. + * A visit is considered unique by comparing using {@code Visit#isSameVisit(Visit)}. As such, adding and updating of + * visits uses Visit#isSameVisit(Visit) for equality so as to ensure that the visit being added or updated is + * unique in terms of identity in the UniqueVisitList. However, the removal of a visit uses Visit#equals(Object) so + * as to ensure that the visit with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + */ +public class UniqueVisitList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent visit as the given argument. + */ + public boolean contains(Visit toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameVisit); + } + + /** + * Adds a visit to the list. + * The visit must not already exist in the list. + */ + public void add(Visit toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateVisitException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the visit {@code target} in the list with {@code editedVisit}. + * {@code target} must exist in the list. + * The visit identity of {@code editedVisit} must not be the same as another existing visit in the list. + */ + public void setVisit(Visit target, Visit editedVisit) { + requireAllNonNull(target, editedVisit); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new VisitNotFoundException(); + } + + if (!target.isSameVisit(editedVisit) && contains(editedVisit)) { + throw new DuplicateVisitException(); + } + + internalList.set(index, editedVisit); + } + + /** + * Removes the equivalent visit from the list. + * The visit must exist in the list. + */ + public void remove(Visit toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new VisitNotFoundException(); + } + } + + public void setVisits(UniqueVisitList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code visits}. + * {@code visits} must not contain duplicate visits. + */ + public void setVisits(List visits) { + requireAllNonNull(visits); + if (!visitsAreUnique(visits)) { + throw new DuplicateVisitException(); + } + + internalList.setAll(visits); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof UniqueVisitList)) { + return false; + } + + UniqueVisitList otherUniqueVisitList = (UniqueVisitList) other; + return internalList.equals(otherUniqueVisitList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code visits} contains only unique visits. + */ + private boolean visitsAreUnique(List visits) { + for (int i = 0; i < visits.size() - 1; i++) { + for (int j = i + 1; j < visits.size(); j++) { + if (visits.get(i).isSameVisit(visits.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/visit/Visit.java b/src/main/java/seedu/address/model/visit/Visit.java new file mode 100644 index 00000000000..3d067ebebb0 --- /dev/null +++ b/src/main/java/seedu/address/model/visit/Visit.java @@ -0,0 +1,128 @@ +package seedu.address.model.visit; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Diagnosis; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; + + +/** + * Represents a single Patient Visit + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Visit { + private final Nric nric; + private DateOfVisit dateOfVisit; + private Symptom symptom; + private Diagnosis diagnosis; + private Status status; + /** + * Every mandatory field must be present and not null. + */ + public Visit(Nric nric, DateOfVisit dateOfVisit, Symptom symptom, Diagnosis diagnosis, Status status) { + //Only the fields that are mandatory are included down here + requireAllNonNull(nric, dateOfVisit, diagnosis, symptom, status); + this.nric = nric; + this.dateOfVisit = dateOfVisit; + this.symptom = symptom; + this.diagnosis = diagnosis; + this.status = status; + } + + public Nric getNric() { + return nric; + } + + public DateOfVisit getDateOfVisit() { + return this.dateOfVisit; + } + public Symptom getSymptom() { + return this.symptom; + } + public Diagnosis getDiagnosis() { + return this.diagnosis; + } + public Status getStatus() { + return this.status; + } + + /** + * Returns true if the person has all mandatory fields. + */ + public static boolean isValidVisit(Visit visit) { + return visit.nric != null + && visit.dateOfVisit != null + && visit.symptom != null + && visit.diagnosis != null + && visit.status != null; + } + + /** + * Returns true if both visits have the same nric and date + * NRIC and Date of Visit serve as the unique key for each Visit + * This is different from equals + * @return Boolean if two Visits are the same + */ + public boolean isSameVisit(Visit otherVisit) { + if (otherVisit == this) { + return true; + } + + return otherVisit != null && otherVisit.getNric().equals(getNric()) + && otherVisit.getDateOfVisit().equals(getDateOfVisit()); + } + + + /** + * Returns true if both persons have the same identity and all data fields. + * This defines a stronger notion of equality between two persons. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Visit)) { + return false; + } + Visit otherVisit = (Visit) other; + if (!(isValidVisit(this) && isValidVisit(otherVisit))) { + return false; + } + return nric.equals(otherVisit.nric) + && dateOfVisit.equals(otherVisit.dateOfVisit) + && symptom.equals(otherVisit.symptom) + && diagnosis.equals(otherVisit.diagnosis) + && status.equals(otherVisit.status); + + + + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(nric, dateOfVisit, symptom, diagnosis, status); + } + + /** + * @return String representation of Visit class + */ + @Override + public String toString() { + // list view + return new ToStringBuilder(this) + .add("nric", nric) + .add("date of visit", dateOfVisit) + .add("symptom", symptom) + .add("diagnosis", diagnosis) + .add("status", status).toString(); + } +} diff --git a/src/main/java/seedu/address/model/visit/VisitContainsNricPredicate.java b/src/main/java/seedu/address/model/visit/VisitContainsNricPredicate.java new file mode 100644 index 00000000000..8fcc41e2b83 --- /dev/null +++ b/src/main/java/seedu/address/model/visit/VisitContainsNricPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.visit; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Visit}'s {@code Nric} matches any of the keywords given. + */ +public class VisitContainsNricPredicate implements Predicate { + private final String keywords; + + public VisitContainsNricPredicate(String keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Visit visit) { + return StringUtil.containsWordIgnoreCase(visit.getNric().toString(), keywords); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof VisitContainsNricPredicate)) { + return false; + } + + VisitContainsNricPredicate otherVisitContainsNricPredicate = (VisitContainsNricPredicate) other; + return keywords.equals(otherVisitContainsNricPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} + diff --git a/src/main/java/seedu/address/model/visit/exceptions/DuplicateVisitException.java b/src/main/java/seedu/address/model/visit/exceptions/DuplicateVisitException.java new file mode 100644 index 00000000000..2b7d1293eff --- /dev/null +++ b/src/main/java/seedu/address/model/visit/exceptions/DuplicateVisitException.java @@ -0,0 +1,11 @@ +package seedu.address.model.visit.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateVisitException extends RuntimeException { + public DuplicateVisitException() { + super("Operation would result in duplicate persons"); + } +} diff --git a/src/main/java/seedu/address/model/visit/exceptions/VisitNotFoundException.java b/src/main/java/seedu/address/model/visit/exceptions/VisitNotFoundException.java new file mode 100644 index 00000000000..dc86cc93433 --- /dev/null +++ b/src/main/java/seedu/address/model/visit/exceptions/VisitNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.visit.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class VisitNotFoundException extends RuntimeException { + public VisitNotFoundException() { + super("Visit not found"); + } +} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index f2e015105ae..7e1250f2e3d 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -5,10 +5,11 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ImmuniMate; +import seedu.address.model.ReadOnlyImmuniMate; /** - * Represents a storage for {@link seedu.address.model.AddressBook}. + * Represents a storage for {@link ImmuniMate}. */ public interface AddressBookStorage { @@ -18,28 +19,28 @@ public interface AddressBookStorage { Path getAddressBookFilePath(); /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. + * Returns AddressBook data as a {@link ReadOnlyImmuniMate}. * Returns {@code Optional.empty()} if storage file is not found. * * @throws DataLoadingException if loading the data from storage failed. */ - Optional readAddressBook() throws DataLoadingException; + Optional readAddressBook() throws DataLoadingException; /** * @see #getAddressBookFilePath() */ - Optional readAddressBook(Path filePath) throws DataLoadingException; + Optional readAddressBook(Path filePath) throws DataLoadingException; /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. + * Saves the given {@link ReadOnlyImmuniMate} to the storage. * @param addressBook cannot be null. * @throws IOException if there was any problem writing to the file. */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveAddressBook(ReadOnlyImmuniMate addressBook) throws IOException; /** - * @see #saveAddressBook(ReadOnlyAddressBook) + * @see #saveAddressBook(ReadOnlyImmuniMate) */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; + void saveAddressBook(ReadOnlyImmuniMate addressBook, Path filePath) throws IOException; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..fa3b0e3b4cc 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -1,75 +1,117 @@ package seedu.address.storage; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Optional; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; +import seedu.address.model.person.Allergies; +import seedu.address.model.person.BloodType; +import seedu.address.model.person.Condition; +import seedu.address.model.person.Country; +import seedu.address.model.person.DateOfAdmission; +import seedu.address.model.person.DateOfBirth; +import seedu.address.model.person.Diagnosis; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Sex; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; /** * Jackson-friendly version of {@link Person}. */ class JsonAdaptedPerson { - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - private final String name; private final String phone; - private final String email; + private final String nric; + private final String sex; + private final String status; private final String address; - private final List tags = new ArrayList<>(); + private final String dateOfBirth; + private final Optional email; + private final Optional country; + private final Optional allergies; + private final Optional bloodType; + private final Optional condition; + private final Optional dateOfAdmission; + private final Optional diagnosis; + private final Optional symptom; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + public JsonAdaptedPerson(@JsonProperty("nric") String nric, @JsonProperty("name") String name, + @JsonProperty("phone") String phone, @JsonProperty("address") String address, + @JsonProperty("dateOfBirth") String dob, @JsonProperty("sex") String sex, + @JsonProperty("status") String status, @JsonProperty("email") String email, + @JsonProperty("country") String country, @JsonProperty("allergies") String allergies, + @JsonProperty("bloodType") String bloodType, @JsonProperty("condition") String condition, + @JsonProperty("dateOfAdmission") String doa, @JsonProperty("diagnosis") String diagnosis, + @JsonProperty("symptom") String symptom) { + this.nric = nric; this.name = name; this.phone = phone; - this.email = email; this.address = address; - if (tags != null) { - this.tags.addAll(tags); - } + this.dateOfBirth = dob; + this.sex = sex; + this.status = status; + this.email = Optional.ofNullable(email); + this.country = Optional.ofNullable(country); + this.allergies = Optional.ofNullable(allergies); + this.bloodType = Optional.ofNullable(bloodType); + this.condition = Optional.ofNullable(condition); + this.dateOfAdmission = Optional.ofNullable(doa); + this.diagnosis = Optional.ofNullable(diagnosis); + this.symptom = Optional.ofNullable(symptom); } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Person} into this class for Json use. */ public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); + this.nric = source.getNric().toString(); + this.name = source.getName().toString(); + this.phone = source.getPhone().toString(); + this.address = source.getAddress().toString(); + this.dateOfBirth = source.getDateOfBirth().toString(); + this.sex = source.getSex().toString(); + this.status = source.getStatus().toString(); + this.email = Optional.ofNullable(source.getEmail()).map(Email::toString); + this.country = Optional.ofNullable(source.getCountry()).map(Country::toString); + this.allergies = Optional.ofNullable(source.getAllergies()).map(Allergies::toString); + this.bloodType = Optional.ofNullable(source.getBloodType()).map(BloodType::toString); + this.condition = Optional.ofNullable(source.getCondition()).map(Condition::toString); + this.dateOfAdmission = Optional.ofNullable(source.getDateOfAdmission()).map(DateOfAdmission::toString); + this.diagnosis = Optional.ofNullable(source.getDiagnosis()).map(Diagnosis::toString); + this.symptom = Optional.ofNullable(source.getSymptom()).map(Symptom::toString); } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Json-friendly adapted person object into the model's {@code Person} object. * * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); - } + Person person; + + // NRIC Check + if (nric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = new Nric(nric); + // Name Check if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -77,7 +119,7 @@ public Person toModelType() throws IllegalValueException { throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); } final Name modelName = new Name(name); - + // Phone Check if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } @@ -85,15 +127,7 @@ public Person toModelType() throws IllegalValueException { throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); } final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - + // Address Check if (address == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); } @@ -101,9 +135,75 @@ public Person toModelType() throws IllegalValueException { throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); } final Address modelAddress = new Address(address); + // Date of Birth Check + if (dateOfBirth == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateOfBirth.class.getSimpleName())); + } + if (!DateOfBirth.isValidDateOfBirth(dateOfBirth)) { + throw new IllegalValueException(DateOfBirth.MESSAGE_CONSTRAINTS); + } + final DateOfBirth modelDateOfBirth = new DateOfBirth(dateOfBirth); + // Sex Check + if (sex == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Sex.class.getSimpleName())); + } + if (!Sex.isValidSex(sex)) { + throw new IllegalValueException(Sex.MESSAGE_CONSTRAINTS); + } + final Sex modelSex = new Sex(sex); + // Status Check + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Status.class.getSimpleName())); + } + if (!Status.isValidStatus(status)) { + throw new IllegalValueException(Status.MESSAGE_CONSTRAINTS); + } + final Status modelStatus = new Status(status); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + person = new Person(modelNric, modelName, modelPhone, modelAddress, modelDateOfBirth, modelSex, modelStatus); + + // Email check + if (email.isPresent()) { + final Email modelEmail = new Email(email.get()); + person.setEmail(modelEmail); + } + // Country check + if (country.isPresent()) { + final Country modelCountry = new Country(country.get()); + person.setCountry(modelCountry); + } + // Allergies check + if (allergies.isPresent()) { + final Allergies modelAllergies = new Allergies(allergies.get()); + person.setAllergies(modelAllergies); + } + // BloodType check + if (bloodType.isPresent()) { + final BloodType modelBloodType = new BloodType(bloodType.get()); + person.setBloodType(modelBloodType); + } + //Condition check + if (condition.isPresent()) { + final Condition modelCondition = new Condition(condition.get()); + person.setCondition(modelCondition); + } + //Date of Admission check + if (dateOfAdmission.isPresent()) { + final DateOfAdmission modelDoa = new DateOfAdmission(dateOfAdmission.get()); + person.setDateOfAdmission(modelDoa); + } + //Diagnosis check + if (diagnosis.isPresent()) { + final Diagnosis modelDiagnosis = new Diagnosis(diagnosis.get()); + person.setDiagnosis(modelDiagnosis); + } + //Symptom check + if (symptom.isPresent()) { + final Symptom modelSymptom = new Symptom(symptom.get()); + person.setSymptom(modelSymptom); + } + return person; } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedVisit.java b/src/main/java/seedu/address/storage/JsonAdaptedVisit.java new file mode 100644 index 00000000000..7e79bdefc8c --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedVisit.java @@ -0,0 +1,106 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Diagnosis; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.person.Status; +import seedu.address.model.person.Symptom; +import seedu.address.model.visit.DateOfVisit; +import seedu.address.model.visit.Visit; + + +/** + * Jackson-friendly version of {@link Person}. + */ +class JsonAdaptedVisit { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Visit's %s field is missing!"; + private final String nric; + private final String dov; + private final String diagnosis; + private final String symptom; + private final String status; + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedVisit(@JsonProperty("nric") String nric, @JsonProperty("dateOfVisit") String dov, + @JsonProperty("diagnosis") String diagnosis, @JsonProperty("symptom") String symptom, + @JsonProperty("status") String status) { + this.nric = nric; + this.dov = dov; + this.status = status; + this.diagnosis = diagnosis; + this.symptom = symptom; + } + + /** + * Converts a given {@code Person} into this class for Json use. + */ + public JsonAdaptedVisit(Visit source) { + this.nric = source.getNric().toString(); + this.dov = source.getDateOfVisit().toString(); + this.diagnosis = source.getDiagnosis().toString(); + this.symptom = source.getSymptom().toString(); + this.status = source.getStatus().toString(); + } + + /** + * Converts this Json-friendly adapted person object into the model's {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Visit toModelType() throws IllegalValueException { + + Visit visit; + + // NRIC Check + if (nric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = new Nric(nric); + // DateOfVisit Check + if (dov == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateOfVisit.class.getSimpleName())); + } + if (!DateOfVisit.isValidDateOfVisit(dov)) { + throw new IllegalValueException(DateOfVisit.MESSAGE_CONSTRAINTS); + } + final DateOfVisit modelDov = new DateOfVisit(dov); + // Symptom check + if (symptom == null || symptom.isEmpty()) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Symptom.class.getSimpleName())); + } + final Symptom modelSymptom = new Symptom(symptom); + // Diagnosis check + if (diagnosis == null || diagnosis.isEmpty()) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Diagnosis.class.getSimpleName())); + } + if (!Diagnosis.isValidDiagnosis(diagnosis)) { + throw new IllegalValueException(Diagnosis.MESSAGE_CONSTRAINTS); + } + final Diagnosis modelDiagnosis = new Diagnosis(diagnosis); + // Status Check + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Status.class.getSimpleName())); + } + if (!Status.isValidStatus(status)) { + throw new IllegalValueException(Status.MESSAGE_CONSTRAINTS); + } + final Status modelStatus = new Status(status); + visit = new Visit(modelNric, modelDov, modelSymptom, modelDiagnosis, modelStatus); + return visit; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index 41e06f264e1..04a486b7d3e 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -12,7 +12,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyImmuniMate; /** * A class to access AddressBook data stored as a json file on the hard disk. @@ -32,7 +32,7 @@ public Path getAddressBookFilePath() { } @Override - public Optional readAddressBook() throws DataLoadingException { + public Optional readAddressBook() throws DataLoadingException { return readAddressBook(filePath); } @@ -42,7 +42,7 @@ public Optional readAddressBook() throws DataLoadingExcepti * @param filePath location of the data. Cannot be null. * @throws DataLoadingException if loading the data from storage failed. */ - public Optional readAddressBook(Path filePath) throws DataLoadingException { + public Optional readAddressBook(Path filePath) throws DataLoadingException { requireNonNull(filePath); Optional jsonAddressBook = JsonUtil.readJsonFile( @@ -60,16 +60,16 @@ public Optional readAddressBook(Path filePath) throws DataL } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + public void saveAddressBook(ReadOnlyImmuniMate addressBook) throws IOException { saveAddressBook(addressBook, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. + * Similar to {@link #saveAddressBook(ReadOnlyImmuniMate)}. * * @param filePath location of the data. Cannot be null. */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveAddressBook(ReadOnlyImmuniMate addressBook, Path filePath) throws IOException { requireNonNull(addressBook); requireNonNull(filePath); diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..edfe9e7b0df 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -9,9 +9,10 @@ import com.fasterxml.jackson.annotation.JsonRootName; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ImmuniMate; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.person.Person; +import seedu.address.model.visit.Visit; /** * An Immutable AddressBook that is serializable to JSON format. @@ -22,13 +23,16 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; private final List persons = new ArrayList<>(); + private final List visits = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("visits") List visits) { this.persons.addAll(persons); + this.visits.addAll(visits); } /** @@ -36,8 +40,9 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List readAddressBook() throws DataLoadingException; + Optional readAddressBook() throws DataLoadingException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveAddressBook(ReadOnlyImmuniMate addressBook) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 8b84a9024d5..00ae72ada5f 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,7 +7,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyImmuniMate; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; @@ -54,23 +54,23 @@ public Path getAddressBookFilePath() { } @Override - public Optional readAddressBook() throws DataLoadingException { + public Optional readAddressBook() throws DataLoadingException { return readAddressBook(addressBookStorage.getAddressBookFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataLoadingException { + public Optional readAddressBook(Path filePath) throws DataLoadingException { logger.fine("Attempting to read data from file: " + filePath); return addressBookStorage.readAddressBook(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + public void saveAddressBook(ReadOnlyImmuniMate addressBook) throws IOException { saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveAddressBook(ReadOnlyImmuniMate addressBook, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); addressBookStorage.saveAddressBook(addressBook, filePath); } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..f41c50f6228 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,5 +1,7 @@ package seedu.address.ui; +import java.util.LinkedList; + import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; @@ -8,6 +10,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; + /** * The UI component that is responsible for receiving user command inputs. */ @@ -17,6 +20,9 @@ public class CommandBox extends UiPart { private static final String FXML = "CommandBox.fxml"; private final CommandExecutor commandExecutor; + private final LinkedList commandHistory = new LinkedList<>(); + private int historyIndex = 0; + private boolean isFirstPress = true; @FXML private TextField commandTextField; @@ -29,6 +35,52 @@ public CommandBox(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + + // Listen for key events on the commandTextField + commandTextField.setOnKeyPressed(event -> { + switch (event.getCode()) { + case UP: + navigateCommandHistory(1); // Navigate backwards in history + event.consume(); // Consume the event to prevent further processing + break; + case DOWN: + navigateCommandHistory(-1); // Navigate forwards in history + event.consume(); + break; + default: + break; + } + }); + } + + + private void navigateCommandHistory(int direction) { + + // Guard Clause when no commands yet + if (commandHistory.isEmpty()) { + return; + } + + // Guard Clause for initial key input. Shows first command without skipping it. + if (historyIndex == 0 && isFirstPress && direction == 1) { + commandTextField.setText(commandHistory.get(historyIndex)); + isFirstPress = false; + return; + } + // Adjust the history index based on the direction + historyIndex += direction; + + // Boundary checks + if (historyIndex < 0) { + historyIndex = 0; + isFirstPress = true; + commandTextField.setText(""); // Clear the text field if we're past the last command + return; + } else if (historyIndex >= commandHistory.size()) { + historyIndex = commandHistory.size(); + } + // Set the commandTextField's text to the command at the new history index + commandTextField.setText(commandHistory.get(historyIndex)); } /** @@ -43,7 +95,10 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandText); + commandHistory.addFirst(commandText); + historyIndex = 0; commandTextField.setText(""); + isFirstPress = true; } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..356b5d9ea78 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-t08-1.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..a0e31dd9eef 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -27,14 +27,12 @@ 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; + private final HelpWindow helpWindow; // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - + private ResultPanel resultPanel; @FXML private StackPane commandBoxPlaceholder; @@ -45,7 +43,7 @@ public class MainWindow extends UiPart { private StackPane personListPanelPlaceholder; @FXML - private StackPane resultDisplayPlaceholder; + private StackPane resultPanelPlaceholder; @FXML private StackPane statusbarPlaceholder; @@ -78,6 +76,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) { @@ -113,8 +112,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + // Result Panel + resultPanel = new ResultPanel(); + resultPanelPlaceholder.getChildren().add(resultPanel.getRoot()); StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); @@ -176,7 +176,7 @@ private CommandResult executeCommand(String commandText) throws CommandException try { CommandResult commandResult = logic.execute(commandText); logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + resultPanel.setFeedbackToUser(commandResult.getFeedbackToUser()); if (commandResult.isShowHelp()) { handleHelp(); @@ -189,7 +189,7 @@ private CommandResult executeCommand(String commandText) throws CommandException return commandResult; } catch (CommandException | ParseException e) { logger.info("An error occurred while executing command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); + resultPanel.setFeedbackToUser(e.getMessage()); throw e; } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..95943e319f2 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,13 +1,13 @@ package seedu.address.ui; -import java.util.Comparator; - import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; import seedu.address.model.person.Person; +import seedu.address.model.person.Status; /** * An UI component that displays information of a {@code Person}. @@ -15,6 +15,10 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; + private static final Color customGreen = Color.web("#81C784"); + private static final Color customYellow = Color.web("#FFF176"); + private static final Color customRed = Color.web("#EF5350"); + /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -33,13 +37,9 @@ public class PersonCard extends UiPart { @FXML private Label id; @FXML - private Label phone; - @FXML - private Label address; + private Label nric; @FXML - private Label email; - @FXML - private FlowPane tags; + private Circle statusCircle; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -48,12 +48,34 @@ public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + name.setText(person.getName().toString() + " (" + person.getSex().toString() + ")"); + nric.setText(person.getNric().toString()); + updateStatusCircle(person.getStatus()); + } + + /** + * Set colour of status circle to represent status + * + * @param status Status of the person + */ + public void updateStatusCircle(Status status) { + switch (status.getStatusType()) { + case HEALTHY: + statusCircle.setFill(customGreen); + statusCircle.setStroke(customGreen); + break; + case UNWELL: + statusCircle.setFill(customRed); + statusCircle.setStroke(customRed); + break; + case PENDING: + statusCircle.setFill(customYellow); + statusCircle.setStroke(customYellow); + break; + default: + statusCircle.setFill(Color.GREY); + statusCircle.setStroke(Color.GREY); + break; + } } } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultPanel.java similarity index 64% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/address/ui/ResultPanel.java index 7d98e84eedf..e72b5fdc610 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultPanel.java @@ -9,20 +9,20 @@ /** * A ui for the status bar that is displayed at the header of the application. */ -public class ResultDisplay extends UiPart { +public class ResultPanel extends UiPart { - private static final String FXML = "ResultDisplay.fxml"; + private static final String FXML = "ResultPanel.fxml"; @FXML - private TextArea resultDisplay; + private TextArea resultPanel; - public ResultDisplay() { + public ResultPanel() { super(FXML); } public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); - resultDisplay.setText(feedbackToUser); + resultPanel.setText(feedbackToUser); } } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..ae5644cdecc 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/stethoscope.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/resources/images/question.png b/src/main/resources/images/question.png new file mode 100644 index 00000000000..11a424ec52a Binary files /dev/null and b/src/main/resources/images/question.png differ diff --git a/src/main/resources/images/search_icon.png b/src/main/resources/images/search_icon.png new file mode 100644 index 00000000000..6bc0e5ec95e Binary files /dev/null and b/src/main/resources/images/search_icon.png differ diff --git a/src/main/resources/images/search_icon_big.png b/src/main/resources/images/search_icon_big.png new file mode 100644 index 00000000000..2e469cd63c9 Binary files /dev/null and b/src/main/resources/images/search_icon_big.png differ diff --git a/src/main/resources/images/search_icon_black.png b/src/main/resources/images/search_icon_black.png new file mode 100644 index 00000000000..27c2d917f70 Binary files /dev/null and b/src/main/resources/images/search_icon_black.png differ diff --git a/src/main/resources/images/stethoscope.png b/src/main/resources/images/stethoscope.png new file mode 100644 index 00000000000..eecd428e9e9 Binary files /dev/null and b/src/main/resources/images/stethoscope.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..aa1736d1c92 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,22 @@ - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..9e654f0b524 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,6 +1,6 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: derive(#b9c4c0, 20%); + background-color: #b9c4c0; /* Used in the default.html file */ } .label { @@ -40,9 +40,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: #b9c4c0; + -fx-control-inner-background: #b9c4c0; + -fx-background-color: #b9c4c0; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -77,20 +77,20 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-background-color: derive(#b9c4c0, 20%); + -fx-border-color: transparent transparent transparent #b9c4c0; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#b9c4c0, 20%); } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#b9c4c0, 20%); } .list-cell { @@ -100,60 +100,60 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #ebf2ef; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #dee3e1; } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #97b0a5; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: #b9c4c0; -fx-border-width: 1; } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: #676767; } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Segoe UI Bold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: #676767; } .cell_small_label { -fx-font-family: "Segoe UI"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #676767; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#b9c4c0, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: derive(#b9c4c0, 20%); + -fx-border-color: derive(#b9c4c0, 10%); -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#469073, 30%); } -.result-display { - -fx-background-color: transparent; +.result-panel { + -fx-background-color: #a7b0ae; -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-font-size: 15pt; + -fx-text-fill: #424242; } -.result-display .label { +.result-panel .label { -fx-text-fill: black !important; } @@ -165,8 +165,8 @@ } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(#469073, 30%); + -fx-border-color: derive(#469073, 25%); -fx-border-width: 1px; } @@ -175,25 +175,25 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#b9c4c0, 30%); + -fx-border-color: derive(#b9c4c0, 30%); -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#b9c4c0, 30%); } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#b9c4c0, 50%); } .context-menu .label { - -fx-text-fill: white; + -fx-text-fill: black; } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#b9c4c0, 20%); } .menu-bar .label { @@ -204,7 +204,7 @@ } .menu .left-container { - -fx-background-color: black; + -fx-background-color: #b9c4c0; } /* @@ -217,10 +217,10 @@ -fx-border-color: #e2e2e2; -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; + -fx-background-color: #b9c4c0; -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; + -fx-text-fill: #676767; -fx-background-insets: 0 0 0 0, 0, 1, 2; } @@ -243,7 +243,7 @@ .button:disabled, .button:default:disabled { -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; + -fx-background-color: #b9c4c0; -fx-text-fill: white; } @@ -257,11 +257,11 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: #FDFFFE; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: #b9c4c0; } .dialog-pane > *.label.content { @@ -271,7 +271,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(#b9c4c0, 25%); } .dialog-pane:header *.header-panel *.label { @@ -282,11 +282,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#fdfffe, 20%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#b9c4c0, 50%); -fx-background-insets: 3; } @@ -314,26 +314,27 @@ #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #676767; } #commandTextField { -fx-background-color: transparent #383838 transparent #383838; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: #383838 #383838 #383838 #383838; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; + -fx-font-family: "Roboto"; + -fx-font-size: 16pt; -fx-text-fill: white; + -fx-prompt-text-fill: white; } #filterField, #personListPanel, #personWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } -#resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; +#resultPanel .content { + -fx-background-color: #a7b0ae, #a7b0ae, #a7b0ae, #a7b0ae; -fx-background-radius: 0; } diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..647824200a2 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -5,7 +5,7 @@ .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: #FDFFFE; } .tag-selector { diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index e01f330de33..ca5d708327e 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..7397094111a 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,55 +6,60 @@ - + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..8405be6fb91 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,54 @@ + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml deleted file mode 100644 index 01b691792a9..00000000000 --- a/src/main/resources/view/ResultDisplay.fxml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - -