diff --git a/.gitignore b/.gitignore index 823d175eb670..baaf23fcee24 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ classes/ /bin/ src/main/resources/docs/ out/ +config.json +reposense/config.json +tokens +StoredCredentials + diff --git a/README.adoc b/README.adoc index 450054624f48..50192c526941 100644 --- a/README.adoc +++ b/README.adoc @@ -1,10 +1,10 @@ -= Address Book (Level 4) += Health Book ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] +https://travis-ci.org/CS2103-AY1819S1-T10-2/main[image:https://travis-ci.org/CS2103-AY1819S1-T10-2/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/elroyhaw/main/branch/master[image:https://ci.appveyor.com/api/projects/status/o0tls33cceuwbwew/branch/master?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-T10-2/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-T10-2/main/badge.svg?branch=master[Coverage Status]] +https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy badge]] https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] ifdef::env-github[] @@ -15,24 +15,22 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* This is a desktop Health Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). * It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. * It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* The Health Book application targets clinic receptionists who need to manage a significant number of appointments +* The Health Book application is most effective when user has preference for CLI and is able to type fast rather than mouse input + == Site Map * <> * <> -* <> * <> * <> == Acknowledgements - +* The original source code was taken from https://github.com/se-edu/addressbook-level4/[AddressBook-Level4] project, created by SE-EDU initiative. * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..bff059d5dee6 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,26 @@ +{ + "authors": + [ + { + "githubId": "florafong97", + "displayName": "FONG ...FLORA", + "authorNames": ["florafong97"] + }, + { + "githubId": "giamjuxian", + "displayName": "GIAM ... XIAN", + "authorNames": ["giamjuxian", "Giam", "giam_ju_xian", "qolas", "giam"] + }, + { + "githubId": "elroyhaw", + "displayName": "HAW Z...ELROY", + "authorNames": ["elroyhaw"] + }, + { + "githubId": "YangYafei1998", + "displayName": "YANG ...YAFEI", + "authorNames": ["YangYafei1998"] + } + ] +} + diff --git a/build.gradle b/build.gradle index f8e614f8b49b..42c2f349812d 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,9 @@ test { dependencies { String testFxVersion = '4.0.12-alpha' String jUnitVersion = '5.1.0' - + compile 'com.google.api-client:google-api-client:1.23.0' + compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' + compile 'com.google.apis:google-api-services-calendar:v3-rev355-1.25.0' implementation group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -82,7 +84,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'healthbook.jar' destinationDir = file("${buildDir}/jar/") } diff --git a/config.json b/config.json new file mode 100644 index 000000000000..db16b168df3f --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "appTitle" : "Health Book", + "logLevel" : "INFO", + "userPrefsFilePath" : "preferences.json" +} diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..36319334bbc1 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,44 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +HealthBook was developed by CS2103 T10-2 team. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Haw Zhe Hao Elroy +image::elroyhaw.png[width="150", align="left"] +{empty} [https://github.com/elroyhaw[github]] [<>] -Role: Project Advisor +Role: Team Lead ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Giam Ju Xian +image::giamjuxian.png[width="150", align="left"] +{empty}[http://github.com/giamjuxian[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer and Testing + +Responsibilities: Ensures the testing of the project is done properly and on time. ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Fong Mei Yee Flora +image::florafong97.png[width="150", align="left"] +{empty}[http://github.com/florafong97[github]] [<>] -Role: Developer + -Responsibilities: Data +Role: Documentation and Code Quality + +Responsibilities: Looks after code quality, ensures adherence to coding standards, and the quality of various project documents. ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Yang Yafei +image::yangyafei1998.png[width="150", align="left"] +{empty}[http://github.com/yangyafei1998[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Deliverables and Deadlines + +Responsibilities: Ensure project deliverables are done on time and in the right format. -''' - -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] - -Role: Developer + -Responsibilities: UI ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..28acf6b3c69e 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -4,4 +4,4 @@ * *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `elroyhaw [at] gmail.com`, `giamjuxian [at] gmail.com`, `floramyy [at] gmail.com`, `faye.yangyafei [at] gmail.com` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..86b499158144 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += HealthBook - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,9 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-T10-2/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team CS2103-AY1819S1-T10-2` Since: `Aug 2018` Licence: `MIT` == Setting up @@ -100,6 +100,7 @@ When you are ready to start coding, == Design +// tag::architecture[] [[Design-Architecture]] === Architecture @@ -125,6 +126,7 @@ The rest of the App consists of four components. * <>: The UI of the App. * <>: The command executor. +* <>: The command executor. * <>: Holds the data of the App in-memory. * <>: Reads data from, and writes data to, the hard disk. @@ -136,22 +138,23 @@ Each of the four components For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. .Class Diagram of the Logic Component -image::LogicClassDiagram.png[width="800"] +image::LogicComponentClassDiagram.png[width="800"] +// end::architecture[] [discrete] ==== Events-Driven nature of the design -The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete-patient n/John Doe`. -.Component interactions for `delete 1` command (part 1) +.Component interactions for `delete-patient n/John Doe` command (part 1) image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `HealthBookChangedEvent` when the HealthBook data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. -.Component interactions for `delete 1` command (part 2) +.Component interactions for `delete-patient n/John Doe` command (part 2) image::SDforDeletePersonEventHandling.png[width="800"] [NOTE] @@ -159,6 +162,7 @@ Note how the event is propagated through the `EventsCenter` to the `Storage` and The sections below give more details of each component. +// tag::uicomponent[] [[Design-Ui]] === UI component @@ -167,7 +171,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `InformationPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -176,59 +180,78 @@ The `UI` component, * Executes user commands using the `Logic` component. * Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. * Responds to events raised from various parts of the App and updates the UI accordingly. +// end::uicomponent[] +// tag::logicomponent[] [[Design-Logic]] === Logic component [[fig-LogicClassDiagram]] .Structure of the Logic Component -image::LogicClassDiagram.png[width="800"] +image::LogicComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `HealthBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. . The command execution can affect the `Model` (e.g. adding a person) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete-patient n/John Doe")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command +.Interactions Inside the Logic Component for the `delete-patient n/John Doe` Command image::DeletePersonSdForLogic.png[width="800"] +// end::logicomponent[] +// tag::calendarcomponent[] +[[Design-Calendar]] +=== Calendar component + +[[fig-CalendarClassDiagram]] +.Structure of the Calendar Component +image::CalendarComponentClassDiagram.png[width="300"] + +*API* : +link:{repoURL}/src/main/java/seedu/address/calendar/GoogleCalendar.java[`GoogleCalendar.java`] + +The `Calendar`, + +* uses Google Calendar API to access the users Google calendar to read and write information to their calendar. +* stores the OAuth of users google calendar data in tokens folder. +// end::calendarcomponent[] + +// tag::modelcomponent[] [[Design-Model]] === Model component .Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. +* stores the HealthBook data. * exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. +// end::modelcomponent[] -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:ModelClassBetterOopDiagram.png[width="800"] - +// tag::storagecomponent[] [[Design-Storage]] === Storage component .Structure of the Storage Component -image::StorageClassDiagram.png[width="800"] +image::StorageComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the HealthBook data in xml format and read it back. +// end::storagecomponent[] [[Design-Commons]] === Common classes @@ -239,92 +262,329 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== Undo/Redo feature +// tag::registerpatient[] +=== Register Patient Feature ==== Current Implementation +The register patient are facilitated by `RegisterPatientCommand` and `RegisterPatientCommandParser`. The Patient object is +automatically tagged as `Patient`. + +.Register Patient Sequence Diagram +image::RegisterPatientSequenceDiagram.png[width="600"] + +Patient model contains a List of upcoming appointment and List of past appointments. We implemented +the patient model in this way to allow ease of addition of new upcoming appointment to the patient. + +Step 1. The user types in `register-patient` in the command box followed by the parameters `n/John Doe p/91234567 +e/test@test.com a/123 Clementi Road` -The 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: +[WARNING] +Command Exception will be thrown if any of the information is missing or invalid. All fields are mandatory. -* `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 2. Upon hitting enter, the `Patient` will be created and added to the storage of the system -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +[NOTE] +Patient is saved in the storage as person. However, when we retrieve the patient object from storage, it will be +created as a patient as determined by their tag. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +==== Design Considerations +===== Aspect: Data Structure of Upcoming and Past Appointments +* Alternative 1 (Current Choice): Upcoming Appointments stored within a List and Past Appointments stored within a +List. This allows the appointments to be compatible with the Observable List. Sorted list of appointments is not crucial +to the implementation of the HealthBook. Hence, we chose to implement the data structure as a list instead. +** Pros: Compatible and easily to implement with model and storage +** Cons: List is not sorted + +* Alternative 2 (Alternative Choice): Upcoming Appointments stored within a PriorityQueue and Past Appointments stored within an +ArrayList. This allows the appointments to be added in a non chronological order and automatically slotted in the correct position. Past appointments can also be iterated +through easily when required. +** Pros: Appointments can be added in a non chronological order. +** Cons: Need to do parsing before passing to model and storage. Hard to manage. +// end::registerpatient[] + +// tag::registerdoctor[] +=== Register Doctor Feature +==== Current Implementation +The register doctor are facilitated by `RegisterDoctorCommand` and `RegisterDoctorCommandParser`. The Doctor object is +automatically tagged as `Doctor`. -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. +.Register Doctor Sequence Diagram +image::RegisterDoctorSequenceDiagram.png[width="600"] -image::UndoRedoStartingStateListDiagram.png[width="800"] +Step 1. The user types in `register-doctor` in the command box followed by the parameters `n/John Doe p/91234567 e/test@test.com a/123 Clementi Road` -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. +[WARNING] +Command Exception will be thrown if any of the information is missing or invalid. All fields are mandatory. -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] +Step 2. Upon hitting enter, a browser will be opened for user to login to google to register their Google Calendar. -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`. +.Google Login Page +image::GoogleLoginPage.png[width="600"] -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +Step 3. Simultaneously, the `Doctor` will be created and added to the storage of the system. In the case that the user +does not login to google, subsequent access to the HealthBook will be denied. + +[WARNING] +Login to Google Calendar is MANDATORY for registration of doctor. This is to facilitate the features that allows doctor +to interact with the HealthBook. If no login is done, the HealthBook will not proceed. [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`. +Doctor is saved in the storage as person. However, when we retrieve the doctor object from storage, it will be +created as a doctor as determined by their tag. + +==== Design Considerations +===== Aspect: Presenting Google Calendar Login to Doctors +* Alternative 1 (Current Choice): Implement Google Calendar login with a popped up browser. +** Pros: Data transfer for the Google login to be simplified. No additional parsing will be required. In addition, the pop up of the browser + will capture the users attention, decreasing the chance of user missing this login procedure. +** Cons: Temporarily exits the application. + +* Alternative 2 (Alternative Choice): Implement Google Calendar login with the browser panel instead. +Instead of having an additional browser, we load the login page into the Browser Panel. This will facilitate a seamless +login for the login but user might miss the login compared to having a popped up browser. +** Pros: Stays within the application. +** Cons: User may miss the login which is mandatory, causing the application to not respond. + +===== Aspect: Saving Doctor with respect to Google Login +* Alternative 1 (Current Choice): Doctor object will not be created and application will not respond if login is +unsuccessful. From the integration used for Google OAuth, it is mandatory for user to login before any process can +continue. This function as our features is complimentary for our application as HealthBook will work at its best with +the google calendar login. +** Pros: No requirement to implement a new Google OAuth. Easy implementation. +** Cons: Application may not respond if login is not done. + +* Alternative 2 (Alternative Choice): Allow doctor to still be created with or without the Google Login. Implement +register-doctor to have Google Login as optional. However, subsequent calls of Calendar related functions will still +prompt the user to login. +** Pros: Flexible login feature. +** Cons: Hard to implement. Need to recreate a Google OAuth system. Features of the HealthBook not utilized. +// end::registerdoctor[] + +// tag::deleteperson[] +=== Delete Patient and Doctor Feature +==== Current Implementation +The delete patient and doctor features are facilitated by `DeletePatientCommand` and `DeleteDoctorCommand`. Both extends +`DeleteCommand` such that it is able to delete a specific subclass of `Person` (i.e. `Patient` or `Doctor`) instead. Additionally, +instead of deletion by index, deletion by name is used in this implementation. + +.Delete Patient Sequence Diagram +image::DeletePatientSequenceDiagram.png[width="600"] -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. +.Delete Doctor Sequence Diagram +image::DeleteDoctorSequenceDiagram.png[width="600"] + +Step 1. The user types in `delete-patient` or `delete-doctor` in the command box followed by the parameters `n/John Doe p/12345678` + +Step 2. Upon hitting enter, the `Patient` or `Doctor` will be removed from the system. + +==== Design Considerations +===== Aspect: Algorithm to find `Patient` or `Doctor` to delete +* Alternative 1 (Current choice): Loop through `model.getFilteredPersonList()` to find `Patient` or `Doctor` to delete +** Pros: Easy to implement +** Cons: If size of list grows unusually large (unusual because of our target audience), it may take awhile to retrieve the +`Patient` or `Doctor` + +* Alternative 2: Use a Map that maps name to `Patient` or `Doctor` +** Pros: Fast retrieval +** Cons: Will have to change a lot of lines of code +// end::deleteperson[] + +// tag::viewperson[] +=== View Patient and Doctor Feature +==== Current Implementation +The view patient and doctor features are facilitated by `ViewPatientCommand` and `ViewDoctorCommand`. This feature shows information +of the person corresponding to the name specified. + +.View Patient Sequence Diagram +image::ViewPatientSequenceDiagram.png[width="600"] + +.View Doctor Sequence Diagram +image::ViewDoctorSequenceDiagram.png[width="600"] + +Step 1. The user types in `view-patient` or `view-doctor` in the command box followed by the parameters `n/John Doe` + +Step 2. Upon hitting enter, information of `Patient` or `Doctor` will be reflected on the main information panel +// end::viewperson[] + +// tag::filterperson[] +=== Filter Patient and Filter Doctor Feature +==== Current Implementation +This feature help to filter all patients/doctors in HealthBook + +.Filter Patient Sequence Diagram +image::FilterPatientSequenceDiagram.png[width="600"] + +Step 1. The user types `filter-patient` or `filter-doctor` in the command box + +Step 2. Upon hitting enter, all persons who are tagged as `Patient` or `Doctor` would appear on the panel +// end::filterperson[] + +// tag::addappointment[] +=== Add Appointment Feature +==== Current Implementation +The add appointment features are facilitated by `AddAppointmentCommand`. This feature allows user to add appointments in the HealthBook +which will be added to the respective `Patient` and `Doctor` of the `Appointment` as well. + +.Add Appointment Sequence Diagram +image::AddAppointmentSequenceDiagram.png[width="600"] + +Step 1. The user types in `add-appointment` in the command box followed by the parameters `np/John Doe nd/Mary Jane d/2018-10-17 15:00` + +Step 2. Upon hitting enter, the `Appointment` will be added to the HealthBook, the `Patient`, and the `Doctor` and his/her Google calendar +// end::addappointment[] + +// tag::deleteappointment[] +=== Delete Appointment Feature +==== Current Implementation +The delete appointment features are facilitated by `DeleteAppointmentCommand`. This feature allows user to delete appointments in the HealthBook +which will be deleted from the respective `Patient` and `Doctor` of the `Appointment` as well. -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +.Delete Appointment Sequence Diagram +image::DeleteAppointmentSequenceDiagram.png[width="600"] + +Step 1. The user types in `delete-appointment` in the command box followed by the appointment id `10000` + +Step 2. Upon hitting enter, the `Appointment` will be removed from the HealthBook, the `Patient`, and the `Doctor` and his/her Google calendar +// end::deleteappointment[] + +// tag::completeappointment[] +=== Complete Appointment Feature +==== Current Implementation +The complete appointment features are facilitated by `CompleteAppointmentCommand`. This feature allows user to complete existing appointments that has already passed +in the HealthBook which change the status of the `Appointment` from `UPCOMING` to `COMPLETED`. + +.Complete Appointment Sequence Diagram +image::CompleteAppointmentSequenceDiagram.png[width="600"] + +Step 1. The user types in `complete-appointment` in the command box followed by the appointment id `10000` + +Step 2. Upon hitting enter, the status in the `Appointment` will be changed from `UPCOMING` to `COMPLETED` +// end::completeappointment[] + +// tag::addprescription[] +=== Add Prescription Feature +==== Current Implementation +The add prescription feature is facilitated by `AddPrescriptionCommand` and `AddPrescriptionCommandParser` +This command is implemented such that it is able to add a `Prescription` object for a specific `Appointment` specified by appointment id. + +Since `Appointment` currently only stores `Doctor` and `Patient` as `String`, thus appointment with the updated prescription will be updated for both +`Doctor` and `Patient` only if the appointment is upcoming and only updated for `Patient` if the appointment is completed. + +.Add Prescription Sequence Diagram +image::AddPrescriptionSequenceDiagram.png[width="600"] + +Step 1. The user types `add-prescription` in the command box followed by the parameters `APPOINTMENT_ID pn/MEDICINE_NAME pd/DOSAGE pc/CONSUMPTION_PER_DAY`. + +Step 2. Upon hitting enter, the `Prescription` will be created and added to the `Appointment`. + +[WARNING] +Exception will be thrown if a medicine of the same name already exists in appointment regardless of dosage and consumption per day. + +Exception will be thrown if medicine that the patient is allergic to is added to the appointment. + +[NOTE] +Prescription can be added to both upcoming and completed appointments + +==== Design considerations +===== Aspect: How to model Prescription +* Alternative 1 (Current choice) : prescription is modelled by a `Prescription` object with 3 attributes `medicineName` , +`dosage` and `consumptionPerDay` + +* Alternative 2 : `Prescription` object stores attributes `medicineName` as a `String` and `dosage`, `consumptionPerDay` as `int`. +// end::addprescription[] + +// tag::deleteprescription[] +=== Delete Prescription Feature +==== Current Implementation +The delete prescription is facilitated by `DeletePrescriptionCommand` and `DeletePrescriptionCommandParser`. +This command is implemented such that it is able to delete a `Prescription object for a specific `Appointment` specified by appointment id. + +.Delete Prescription Sequence Diagram +image::DeletePrescriptionSequenceDiagram.png[width="600"] + +Step 1. The user types `delete-prescription` in the command box followed by the parameters `APPOINTMENT_ID pn/MEDICINE_NAME`. + +Step 2. Upon hitting enter, the `Prescription` will be deleted from the `Appointment`. + +[WARNING] +Exception will be thrown if medicine to be deleted does not exist in appointment. [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book 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. +Prescription can be deleted from both upcoming and completed appointments +// end::deleteprescription[] -The following sequence diagram shows how the undo operation works: +// tag::addmedicalhistory[] +=== Add Medical History Feature +==== Current Implementation +The add medical history feature is facilitated by `AddMedicalHistoryCommand` and `AddMedicalHistoryCommandParser`. This +command is implemented such that it is able to add `Allergy` AND/OR `Condition` to `MedicalHistory` object for a specific +`Patient` specified by name. -image::UndoRedoSequenceDiagram.png[width="800"] +.Add Medical History Sequence Diagram +image::AddMedicalHistorySequenceDiagram.png[width="600"] -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. The user types in `add-medical-history` in the command box followed by the parameter `n/John Doe al/penicillin,milk +c/subhealthy,hyperglycemia` [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book 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. +For multiple inputs in the same field, use comma `,` to separate. +You can only omit either ALLERGY parameter or CONDITION parameter. +Allergies and conditions should only contain alphanumeric characters and spaces, and they should not be blank. -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 2. Upon hitting enter, the `allergies` AND/OR `conditions` of `MedicalHistory` will get added -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] +[WARNING] +Exception will be thrown if inputs for both field are blank. -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. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +==== Design Consideration +===== Aspect: The way to model MedicalHistory +* Alternative 1 (Current choice): Model `Allergy` and `Condition` as classes. +`AddMedicalHistoryCommand` contains ArrayLists of `Allergy` and `Condition`. +* Alternative 2 : Treat allergy and condition as Strings. `AddMedicalHistoryCommand` contains two ArrayLists of String. +// end::addmedicalhistory[] -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] +// tag::deletemedicalhistory[] +=== Delete Medical History Feature +==== Current Implementation +The delete medical history feature is facilitated by `DeleteMedicalHistoryCommand` and +`DeleteMedicalHistoryCommandParser`. This command is implemented such that it is able to delete `Allergy` AND/OR +`Condition` from `MedicalHistory` object for a specific `Patient` specified by name. -The following activity diagram summarizes what happens when a user executes a new command: +.Delete Medical History Sequence Diagram +image::DeleteMedicalHistorySequenceDiagram.png[width="600"] -image::UndoRedoActivityDiagram.png[width="650"] +Step 1. The user types in `delete-medical-history` in the command box followed by the parameter `n/John Doe +al/penicillin,milk c/subhealthy,hyperglycemia` -==== Design Considerations +[NOTE] +For multiple inputs in the same field, use comma `,` to separate. +You can only omit either ALLERGY parameter or CONDITION parameter. +Allergies and conditions should only contain alphanumeric characters and spaces, and they should not be blank. -===== Aspect: How undo & redo executes +Step 2. Upon hitting enter, the `Allergy` AND/OR `Condition` of `MedicalHistory` will get deleted -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +[WARNING] +Exception will be thrown if inputs for both field are blank. +Exception will be thrown if given input is not in the original medical history. -===== Aspect: Data structure to support the undo/redo commands +===== Aspect: The way to model MedicalHistory +* Alternative 1 (Current choice): Model `Allergy` and `Condition` as classes. +`AddMedicalHistoryCommand` contains ArrayLists of `Allergy` and `Condition`. +* Alternative 2 : Treat allergy and condition as Strings. `AddMedicalHistoryCommand` contains two ArrayLists of String. +// end::deletemedicalhistory[] -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +// tag::remark[] +=== Remark Feature +==== Current Implementation +Remark command allows user to input any information into a Person object with no restrictions. +If an input is empty, the previous existing remark is deleted. This command can only be called after person is created. -// tag::dataencryption[] -=== [Proposed] Data Encryption +.Remark Command Sequence Diagram +image::RemarkCommandSequenceDiagram.png[width="600"] -_{Explain here how the data encryption feature will be implemented}_ +Step 1. The user types in `remark` in the command box followed by the parameters `appointment_id r/some remark` -// end::dataencryption[] +Step 2. Upon hitting enter, the new remark will be visible in the Doctor/Patient's contact. If input was empty, the +previous remark will be deleted. +// end::remark[] === Logging @@ -528,7 +788,7 @@ Here are the steps to create a new release. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, HealthBook depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) @@ -560,9 +820,9 @@ Do take a look at <> before attempting to modify the `Logic` compo **** * Hints ** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. +** link:{repoURL}/src/main/java/seedu/address/logic/parser/HealthBookParser.java[`HealthBookParser`] is responsible for analyzing command words. * Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. +** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/HealthBookParser.java[`HealthBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. ** Add new tests for each of the aliases that you have added. ** Update the user guide to document the new aliases. ** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. @@ -571,21 +831,21 @@ Do take a look at <> before attempting to modify the `Logic` compo [discrete] ==== `Model` component -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the HealthBook, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. [TIP] Do take a look at <> before attempting to modify the `Model` component. -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. +. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the HealthBook. + **** * Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. +** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/HealthBook.java[`HealthBook`] API need to be updated. ** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. +** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/HealthBook.java[`HealthBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/HealthBook.java[`HealthBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. * Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. +** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/HealthBook.java[`HealthBook`]. Loop through each person, and remove the `tag` from each person. +** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `HealthBook#removeTag(Tag)`. ** Add new tests for each of the new public methods that you have added. ** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. **** @@ -593,7 +853,7 @@ Do take a look at <> before attempting to modify the `Model` compo [discrete] ==== `Ui` component -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your HealthBook application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. [TIP] Do take a look at <> before attempting to modify the `UI` component. @@ -638,12 +898,12 @@ image::getting-started-ui-result-after.png[width="200"] ** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. ** You can write two different kinds of tests to ensure that the functionality works: *** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. +*** The system tests link:{repoURL}/src/test/java/systemtests/HealthBookSystemTest.java[`HealthBookSystemTest#assertCommandBoxShowsDefaultStyle() and HealthBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. ** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. *** Do read the commits one at a time if you feel overwhelmed. **** -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the HealthBook. + **Before** + @@ -656,29 +916,29 @@ image::getting-started-ui-status-after.png[width="500"] **** * Hints ** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. +** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the HealthBook is updated. * Solution ** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. +** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleHealthBookChangedEvent(HealthBookChangedEvent)`] to update the number of persons whenever there are new changes to the HealthBook. ** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. +** For system tests, modify link:{repoURL}/src/test/java/systemtests/HealthBookSystemTest.java[`HealthBookSystemTest`] to also verify the new total number of persons status bar. ** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. **** [discrete] ==== `Storage` component -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the HealthBook to the cloud. However, the current implementation of the application constantly saves the HealthBook after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the HealthBook storage. [TIP] Do take a look at <> before attempting to modify the `Storage` component. -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +. Add a new method `backupHealthBook(ReadOnlyHealthBook)`, so that the HealthBook can be saved in a fixed temporary location. + **** * Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. +** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/HealthBookStorage.java[`HealthBookStorage`] interface. +** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlHealthBookStorage.java[`XmlHealthBookStorage`] class. * Solution ** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. **** @@ -688,7 +948,7 @@ Do take a look at <> before attempting to modify the `Storage` c By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +*Scenario:* You are a software maintainer for `HealthBook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. ==== Description Edits the remark for a person specified in the `INDEX`. + @@ -709,12 +969,12 @@ Let's start by teaching the application how to parse a `remark` command. We will **Main:** . Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/HealthBookParser.java[`HealthBookParser`] to accept a `RemarkCommand`. **Tests:** . Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/HealthBookParserTest.java[`HealthBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. ===== [Step 2] Logic: Teach the app to accept 'remark' arguments Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` @@ -723,14 +983,14 @@ Let's teach the application to parse arguments that our `remark` command will ac . Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. . Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/HealthBookParser.java[`HealthBookParser`] to use the newly implemented `RemarkCommandParser`. **Tests:** . Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. . Add `RemarkCommandParserTest` that tests different boundary values for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/HealthBookParserTest.java[`HealthBookParserTest`] to test that the correct command is generated according to the user input. ===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. @@ -763,7 +1023,7 @@ Now we have the `Remark` class, we need to actually use it inside link:{repoURL} . Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. . You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `healthBook.xml` so that the application will load the sample data when you launch it.) ===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. @@ -774,7 +1034,7 @@ We now have `Remark` s for `Person` s, but they will be gone when we exit the ap **Tests:** -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +. Fix `invalidAndValidPersonHealthBook.xml`, `typicalPersonsHealthBook.xml`, `validHealthBook.xml` etc., such that the XML tests will not fail due to a missing `` element. ===== [Step 6b] Test: Add withRemark() for `PersonBuilder` Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. @@ -815,13 +1075,18 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- *Target user profile*: -* has a need to manage a significant number of contacts +* Clinic Receptionists +* has a need to manage a significant number of appointments +* has a need to keep track of history of patients, appointments and prescriptions * prefer desktop apps over other types * can type fast * prefers typing over mouse input * is reasonably comfortable using CLI apps -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*Value proposition*: +Our platform will allow the clinic receptionist to easily manage patient’s medical information +and appointments with the doctors. This will allow appointments to be efficiently managed and +information transfer to be more seamless. [appendix] == User Stories @@ -831,53 +1096,436 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un [width="59%",cols="22%,<23%,<25%,<30%",options="header",] |======================================================================= |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 +|`* * *` |receptionist |register accounts for patients or doctors |keep track of their appointments and other information -|`* * *` |user |add a new person | +|`* * *` |receptionist |delete an account for patients or doctors |remove accounts that are not in use or invalid -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |receptionist |view patient’s upcoming appointments, recent visits and follow-ups requests on the landing page |easily look through the patients information -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |receptionist |add prescription to patient after appointment |keep their latest completed appointment updated -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* * *` |receptionist |delete prescription to patient after appointment |remove any discrepancies found -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* *` |receptionist |edit prescription to patient after appointment |amend any discrepancies found + +|`* * *` |receptionist |check prescription provided by doctor |follow up with the patients if they wish to check their prescription + +|`* * *` |receptionist |add patient details (especially medical history) to patient’s profile |allow the doctors to find the patients records easily + +|`* * *` |receptionist |delete patient details (especially medical history) from patient's profile |allow the doctors to find the most updated patients records + +|`* * *` |receptionist |view patient’s details prior to appointment |provide information to the doctor, allowing them to know the full extent of patient’s health condition + +|`* * *` |receptionist |remind a particular patient of their next appointment |prevent him/her from forgetting their appointments + +|`* *` |receptionist |remind all patients of their next appointment |prevent them from forgetting their appointments + +|`* * *` |receptionist |book an doctor’s appointment for patient |reserve an appointment with doctor for the patient + +|`* * ` |receptionist |edit an doctor’s appointment for patient |edit a reserve slot for an appointment with doctor for the patient -_{More to be added}_ +|`* * *` |receptionist |delete an doctor’s appointment for patient |remove a reserve slot for an appointment with doctor for the patient +|======================================================================= + +// tag::usecases[] [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `HealthBook` and the *Actor* is the `receptionist`, unless specified otherwise) + [discrete] -=== Use case: Delete person +=== Use case: Register Doctor +*MSS* + +1. User requests to register a Doctor into the HealthBook +2. User enters relevant details specified by command +3. HealthBook brings user to google log in page +4. User enters google login details +5. User closes google page and reopens HealthBook +6. HealthBook registers the doctor. ++ +Use case ends. + +*Extensions* + +[none] +2a. User enters wrong format for command. +[none] +** 2a1. HealthBook shows an error message. ++ +Use case resumes at step 2 + +[none] +2b. User enters a duplicate persons. +[none] +** 2b1. HealthBook shows an error message. ++ +Use case resumes at step 2 + +[none] +3a. User closes google page without logging in. +[none] +** 3a1. HealthBook will become unresponsive +** 3a2. User closes and reopens Healthbook ++ +Use case restarts from step 1 + +[discrete] +=== Use case: Register Patient +*MSS* + +1. User requests to register a Patient into the HealthBook +2. User enters relevant details specified by command +3. HealthBook registers the patient. ++ +Use case ends. + +*Extensions* + +[none] +2a. User enters wrong format for command. +[none] +** 2a1. Healthbook shows an error message. ++ +Use case resumes at step 2 + +[none] +2b. User enters a duplicate persons. +[none] +** 2b1. Healthbook shows an error message. ++ +Use case resumes at step 2 + +[discrete] +=== Use case: Delete Doctor/Patient *MSS* 1. User requests to list persons -2. AddressBook shows a list of persons +2. HealthBook shows a list of persons 3. User requests to delete a specific person in the list -4. AddressBook deletes the person +4. HealthBook deletes the person + Use case ends. *Extensions* +[none] +* 2a. The list is empty. ++ +Use case ends + +* 3a. User enters a name that belongs to multiple contacts with different phone numbers ++ +[none] +** 3a1. Healthbook shows an error message that prompts user to enter phone parameter. ++ +Use case resumes at step 2 + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2 + +[discrete] +=== Use case: View person + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to view a specific person in list +4. HealthBook shows the person ++ +Use case ends + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends + +* 3a. User enters a name that belongs to multiple contacts with different phone numbers ++ +[none] +** 3a1. Healthbook shows an error message that prompts user to enter phone parameter. ++ +Use case resumes at step 2 + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2 + +[discrete] +=== Use case: List persons + +*MSS* + +1. User requests to list persons +2. HealthBook shows list of persons. ++ +Use case ends. + +[discrete] +=== Use case: Filter Doctor/Patient + +*MSS* + +1. User requests to filter doctors/patients +2. HealthBook shows list of doctors/patients ++ +Use case ends. + +[discrete] +=== Use case: Add Appointment + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to add an appointment to the HealthBook for a specific Doctor and Patient +4. HealthBook adds the person ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The given Doctor and/or Patient name is has a duplicate contact with a different phone number. ++ +[none] +** 3a1. HealthBook shows an error and prompts user to input a phone number to differentiate. ++ +Use case resumes at step 2 + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2 + +[discrete] +=== Use case: Delete Appointment + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to delete an appointment in the HealthBook for a specific appointmentId +4. HealthBook deletes the appointment ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The given Id is invalid. ++ +[none] +** 3a1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Complete Appointment + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to complete a specific appointment in the list +4. HealthBook completes the appointment + +*Extensions* + [none] * 2a. The list is empty. + Use case ends. -* 3a. The given index is invalid. +[none] +* 3a. The given Id is invalid. ++ +[none] +** 3a1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Add Prescription + +*MSS* + + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to add a specific prescription to a specific appointment in the list +4. HealthBook adds the prescription + +*Extensions* + +[none] +* 3a. The given Id is invalid. ++ +[none] +** 3a1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +* 3b. The given prescription's medicine name already exists in HealthBook + [none] -** 3a1. AddressBook shows an error message. +** 3b1. HealthBook shows an error message. + Use case resumes at step 2. -_{More to be added}_ +* 3c. The given patient is allergic to the given prescription's medicine name. ++ +[none] +** 3c1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Delete Prescription + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to delete a specific prescription from a specific appointment +4. HealthBook deletes the prescription ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given id is invalid. ++ +[none] +** 3a1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +* 3b. The given prescription is invalid. ++ +[none] +** 3b1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Add Medical History + +*MSS* + +1. User requests to list persons/filter-patients +2. HealthBook shows a list of persons/patients +3. User requests to add medical history to a specific person in the list +4. HealthBook adds the medical history of the patient. + +*Extensions* + +[none] +* 3a. The given Patient name has a duplicate contact with a different phone number. ++ +[none] +** 3a1. HealthBook shows an error and prompts user to input a phone number to differentiate. ++ +Use case resumes at step 2. + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2. + +* 3c. User enters a duplicate allergy/condition ++ +[none] +** 3c1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Delete Medical History + +*MSS* + +1. User requests to list persons/filter-patients +2. HealthBook shows a list of persons/patients +3. User requests to delete medical history of a specific person in the list +4. HealthBook deletes the medical history of the patient. + +*Extensions* + +[none] +* 3a. The given Patient name has a duplicate contact with a different phone number. ++ +[none] +** 3a1. HealthBook shows an error and prompts user to input a phone number to differentiate. ++ +Use case resumes at step 2. + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2. + +* 3c. User enters an invalid allergy/ condition ++ +[none] +** 3c1. HealthBook shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Remark person + +*MSS* + +1. User requests to list persons +2. HealthBook shows a list of persons +3. User requests to add/delete a remark to a specific person in the list +4. HealthBook adds/deletes the remark. ++ +Use case ends. + +*Extensions* + +[none] +* 3a. The given Person name has a duplicate contact with a different phone number. ++ +[none] +** 3a1. HealthBook shows an error and prompts user to input a phone number to differentiate. ++ +Use case resumes at step 2. + +* 3b. User enters an invalid name and/or phone ++ +[none] +** 3b1. Healthbook shows an error message. ++ +Use case resumes at step 2. +// end::usecases[] + [appendix] == Non Functional Requirements @@ -885,35 +1533,35 @@ _{More to be added}_ . Should work on any <> as long as it has Java `9` or higher installed. . Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. . 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. +. Requires internet access to carry out operations in the HealthBook -_{More to be added}_ - +// tag::glossary[] [appendix] == Glossary [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others - -[appendix] -== Product Survey - -*Product Name* +Receptionist:: +Target audience -Author: ... +Patient:: +Indirect target audience, patient whom visits the clinic -Pros: +Doctor:: +Indirect target audience, doctor whom works at the clinic -* ... -* ... +Appointment:: +A date and time fixed for a patient to consult a doctor -Cons: +Prescription:: +The medicine prescribed for the appointment -* ... -* ... +Medical History:: +Allergies and/or conditions that a patient may have +// end::glossary[] +// tag::manualtesting[] [appendix] == Instructions for Manual Testing @@ -936,26 +1584,29 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ - -=== Deleting a person +=== Deleting a patient -. Deleting a person while all persons are listed +. Deleting a patient -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. 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. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + +.. Prerequisites: List all persons using the `list` command if `filter-patient` or `filter-doctor` has been called before. +.. Test case: `delete-patient n/John Doe` + + Expected: Patient named John Doe deleted from the HealthBook, along with any appointments tied to him. + Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. +.. Test case: `delete-patient n/jlfsajfl` + + Expected: No patient is deleted if no patient with such name exists. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `delete-patient` + Expected: Similar to previous. -_{ more test cases ... }_ - -=== Saving data +=== Viewing a patient -. Dealing with missing/corrupted data files +. Viewing a patient -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ - -_{ more test cases ... }_ +.. Prerequisites: List all persons using the `list` command if `filter-patient` or `filter-doctor` has been called before. +.. Test case: `view-patient n/John Doe` + + Expected: Information panel reflects information of John Doe from the HealthBook. + Name of patient currently viewing is shown in the status message. Timestamp in the status bar is updated. +.. Test case: `view-patient n/jlfsajfl` + + Expected: No patient is viewed if no patient with such name exists. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `view-patient` + + Expected: Similar to previous. +// end::manualtesting[] diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index 83cda0927226..000000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,266 +0,0 @@ -= Learning Outcomes -:site-section: LearningOutcomes -:toc: macro -:toc-title: -:toclevels: 1 -:sectnums: -:sectnumlevels: 1 -:imagesDir: images -:stylesDir: stylesheets -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -''' - -== Use High-Level Designs `[LO-HighLevelDesign]` - -Note how the <> describes the high-level design using an _Architecture Diagrams_ and high-level sequence diagrams. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/[se-edu/se-book: Design: Architecture] -* https://se-edu.github.io/se-book/design/introduction/multilevelDesign/[se-edu/se-book: Design: Introduction: Multi-Level Design] - -''' - -== Use Event-Driven Programming `[LO-EventDriven]` - -Note how the <> uses events to communicate with components without needing a direct coupling. Also note how the link:{repoURL}/src/main/java/seedu/address/commons/core/index/EventsCenter.java[`EventsCenter.java`] acts as an event dispatcher to facilitate communication between event creators and event consumers. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/architecturalStyles/eventDriven/[se-edu/se-book: Design: Architecture: Architecture Styles: Event-Driven Architectural Style] - -''' - -== Use API Design `[LO-ApiDesign]` - -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -image:LogicClassDiagram.png[width="800"] - -*Resources* - -* https://se-edu.github.io/se-book/reuse/apis/[se-edu/se-book: Implementation: Reuse: APIs] - -''' - -== Use Assertions `[LO-Assertions]` - -Note how the AddressBook app uses Java ``assert``s to verify assumptions. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/assertions/[se-edu/se-book: Implementation: Error Handling: Assertions] - -=== Exercise: Add more assertions - -* Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. - - -''' - -== Use Logging `[LO-Logging]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/logging/[se-edu/se-book: Implementation: Error Handling: Logging] - -=== Exercise: Add more logging - -Add more logging to AddressBook as you see fit. - - -''' - -== Use Defensive Coding `[LO-DefensiveCoding]` - -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/defensiveProgramming/[se-edu/se-book: Implementation: Error Handling: Defensive Programming] - -=== Exercise: identify more places for defensive coding - -Analyze the AddressBook code/design to identify, - -* where defensive coding is used -* where the code can be more defensive - -''' - -== Use Build Automation `[LO-BuildAutomation]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/what/[se-edu/se-book: Implementation: Integration: Build Automation: What] - -=== Exercise: Use gradle to run tasks - -* Use gradle to do these tasks: Run all tests in headless mode, build the jar file. - -=== Exercise: Use gradle to manage dependencies - -* Note how the build script `build.gradle` file manages third party dependencies such as ControlsFx. Update that file to manage a third-party library dependency. - - -''' - -== Use Continuous Integration `[LO-ContinuousIntegration]` - -Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/continuousIntegrationDeployment/[se-edu/se-book: Implementation: Integration: Build Automation: CI & CD] - -=== Exercise: Use Travis in your own project - -* Set up Travis to perform CI on your own fork. - - -''' - -== Use Code Coverage `[LO-CodeCoverage]` - -Note how our CI server <>. (https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]]) After <> for your project, you can visit Coveralls website to find details about the coverage of code pushed to your repo. https://coveralls.io/github/se-edu/addressbook-level4?branch=master[Here] is an example. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testCoverage/[se-edu/se-book: QA: Testing: Test Coverage] - -=== Exercise: Use the IDE to measure coverage locally - -* Use the IDE to measure code coverage of your tests. - -''' - -== Apply Test Case Design Heuristics `[LO-TestCaseDesignHeuristics]` - -The link:{repoURL}/src/test/java/seedu/address/commons/util/StringUtilTest.java[`StringUtilTest.java`] -class gives some examples of how to use _Equivalence Partitions_, _Boundary Value Analysis_, and _Test Input Combination Heuristics_ to improve the efficiency and effectiveness of test cases testing the link:../src/main/java/seedu/address/commons/util/StringUtil.java[`StringUtil.java`] class. - -*Resources* - -* https://se-edu.github.io/se-book/testCaseDesign/[se-edu/se-book: QA: Test Case Design] - -=== Exercise: Apply Test Case Design Heuristics to other places - -* Use the test case design heuristics mentioned above to improve test cases in other places. - -''' - -== Write Integration Tests `[LO-IntegrationTests]` - -Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. - -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. - -Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. - -[source,java] ----- -@Test -public void execute_invalidCommandFormat_throwsParseException() { - ... - assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); - assertHistoryCorrect(invalidCommand); -} ----- - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write unit and integration tests for the same method. - -* Write a unit test for a high-level method somewhere in the code base (or a new method you wrote). -* Write an integration test for the same method. - -''' - -== Write System Tests `[LO-SystemTesting]` - -Note how tests below `src/test/java/systemtests` package (e.g link:{repoURL}/src/test/java/systemtests/AddCommandSystemTest.java[`AddCommandSystemTest.java`]) are system tests because they test the entire system end-to-end. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write more system tests - -* Write system tests for the new features you add. - -''' - -== Automate GUI Testing `[LO-AutomateGuiTesting]` - -Note how this project uses TextFX library to automate GUI testing, including <>. - -=== Exercise: Write more automated GUI tests - -* Covered by `[LO-SystemTesting]` - -''' - -== Apply Design Patterns `[LO-DesignPatterns]` - -Here are some example design patterns used in the code base. - -* *Singleton Pattern* : link:{repoURL}/src/main/java/seedu/address/commons/core/EventsCenter.java[`EventsCenter.java`] is Singleton class. Its single instance can be accessed using the `EventsCenter.getInstance()` method. -* *Facade Pattern* : link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager.java`] is not only shielding the internals of the Storage component from outsiders, it is mostly redirecting method calls to its internal components (i.e. minimal logic in the class itself). Therefore, `StorageManager` can be considered a Facade class. -* *Command Pattern* : The link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command.java`] and its sub classes implement the Command Pattern. -* *Observer Pattern* : The <> used by this code base employs the Observer pattern. For example, objects that are interested in events need to have the `@Subscribe` annotation in the class (this is similar to implementing an `\<>` interface) and register with the `EventsCenter`. When something noteworthy happens, an event is raised and the `EventsCenter` notifies all relevant subscribers. Unlike in the Observer pattern in which the `\<>` class is notifying all `\<>` objects, here the `\<>` classes simply raises an event and the `EventsCenter` takes care of the notifications. -* *MVC Pattern* : -** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. -** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). -* *Abstraction Occurrence Pattern* : Not currently used in the app. - -*Resources* - -* https://se-edu.github.io/se-book/designPatterns/[se-edu/se-book: Design: Design Patterns] - -=== Exercise: Discover other possible applications of the patterns - -* Find other possible applications of the patterns to improve the current design. e.g. where else in the design can you apply the Singleton pattern? -* Discuss pros and cons of applying the pattern in each of the situations you found in the previous step. - -=== Exercise: Find more applicable patterns - -* Learn other _Gang of Four_ Design patterns to see if they are applicable to the app. - -''' - -== Use Static Analysis `[LO-StaticAnalysis]` - -Note how this project uses the http://checkstyle.sourceforge.net/[CheckStyle] static analysis tool to confirm compliance with the coding standard. - -*Resources* - -* https://se-edu.github.io/se-book/qualityAssurance/staticAnalysis/[se-edu/se-book: QA: Static Analysis] - -=== Exercise: Use CheckStyle locally to check style compliance - -* Install the CheckStyle plugin for your IDE and use it to check compliance of your code with our style rules (given in `/config/checkstyle/checkstyle.xml`). - -''' - -== Do Code Reviews `[LO-CodeReview]` - -* Note how some PRs in this project have been reviewed by other developers. Here is an https://github.com/se-edu/addressbook-level4/pull/147[example]. -* Also note how we have used https://www.codacy.com[Codacy] to do automate some part of the code review workload (https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]]) - - -=== Exercise: Review a PR - -* Review PRs created by team members. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..f7c4af25c65d 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += HealthBook - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,19 +12,22 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S1-T10-2/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team CS2103-AY1819S1-T10-2` Since: `Aug 2018` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +HealthBook is for clinic receptionist who *prefer to use a desktop app for managing doctors and patients information and appointments*. +HealthBook is *optimized for receptionist who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). +If you can type fast, HealthBook can help you maintain a clean and concise information management of your doctors and patients more efficiently compared traditional GUI apps. +Interested? Jump to the <> to get started. Enjoy! == Quick Start . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `healthbook.jar` link: https://github.com/CS2103-AY1819S1-T10-2/main/releases. +. Copy the file to the folder you want to use as the home folder for your HealthBook. . Double-click the file to start the app. The GUI should appear in a few seconds. + image::Ui.png[width="790"] @@ -33,9 +36,9 @@ image::Ui.png[width="790"] e.g. typing *`help`* and pressing kbd:[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. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`list`* : lists all patients and doctors +* **`register-patient`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : registers a patient named `John Doe` to the HealthBook. +* **`delete-patient`**`n/John Doe` : deletes patient named John Doe * *`exit`* : exits the app . Refer to <> for details of each command. @@ -46,177 +49,288 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `register-patient n/NAME`, `NAME` is a parameter which can be used as `register-patient n/John Doe`. +* Items in square brackets are optional e.g `n/NAME [al/ALLERGIES] [c/CONDITION]` can be used as `n/John Doe al/milk c/healthy` or as `n/John Doe al/milk`. +* 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. ==== === Viewing help : `help` Format: `help` -=== Adding a person: `add` +// tag::registerpatient[] +=== Registering a patient: `register-patient` +Registers a patient to the HealthBook. Patient is automatically tagged as a "Patient". + +Format: `register-patient n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Examples: + +* `register-patient n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` [TIP] -A person can have any number of tags (including 0) +After registering the patient, all commands that requires the patient's name are non-case sensitive for convenience. +// end::registerpatient[] + +// tag::registerdoctor[] +=== Registering a doctor: `register-doctor` +Registers a doctor to the HealthBook. Doctor is automatically tagged as a "Doctor". + +Format: `register-doctor n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS` + +[WARNING] +Login to Google Calendar is mandatory. If no login is done, the application will not be able to proceed. +Please ensure login is done before proceeding back to the HealthBook. 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` +* `register-doctor n/Mary Jane p/98765432 e/maryj@example.com a/John street, block 123, #01-01` -=== Listing all persons : `list` -Shows a list of all persons in the address book. + -Format: `list` +[TIP] +After registering the doctor, all commands that requires the doctor's name are non-case sensitive. +// end::registerdoctor[] + +// tag::deletepatient[] +=== Deleting a patient: `delete-patient` +Deletes a patient from the HealthBook by name and phone. + +Format: `delete-patient n/NAME [p/PHONE]` + +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. + +Examples: + +* `delete-patient n/Jane Doe` +* `delete-patient n/John Doe p/98264293` +// end::deletepatient[] + +// tag::deletedoctor[] +=== Deleting a doctor: `delete-doctor` +Deletes a doctor from the HealthBook by name and phone. + +Format: `delete-doctor n/NAME [p/PHONE]` + +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. -=== Editing a person : `edit` +Examples: + +* `delete-doctor n/Amanda Jane` +* `delete-doctor n/Mary Jane p/94726274` +// end::deletedoctor[] -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +// tag::viewpatient[] +=== Viewing a patient: `view-patient` +Views a landing page of a patient on the HealthBook. This will display all information of the patient onto the HealthBook +including their medical history, upcoming appointment and their past appointments. + +Format: `view-patient n/NAME [p/PHONE]` + +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* Views the patient and loads the information page of the patient with the specified `NAME` and/or `PHONE`. +* The name/phone refers to the name/phone that the patient is registered under. +* The name/phone must have been registered into the HealthBook. **** 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. +* `view-patient n/John Doe` +* `view-patient n/Jane Doe p/93726273` +// end::viewpatient[] -=== Locating persons by name: `find` +// tag::viewdoctor[] +=== Viewing a doctor: `view-doctor` +Views a landing page of a doctor on the HealthBook. This will display the doctor's upcoming appointments. + +Format: `view-doctor n/NAME [p/PHONE]` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. **** -* 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` +* Views the doctor's upcoming appointments with the specified `NAME` and/or `PHONE`. +* The name/phone refers to the name/phone that the doctor is registered under. +* The name/phone must have been registered into the HealthBook. **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `view-doctor n/Amanda Jane p/93726272` +* `view-doctor n/Mary Jane` +// end::viewdoctor[] -=== Deleting a person : `delete` +=== Listing all patients and doctors : `list` +Shows a list of all patients and doctors in the HealthBook. + +Format: `list` + +// tag::filterpatient[] +=== Filtering patients from list : `filter-patient` +Shows a list of all patients in the HealthBook. + +Format: `filter-patient` + +[WARNING] +`list` command has to be called after `filter-patient` is called before proceeding +with other command operations +// end::filterpatient[] + +// tag::filterdoctor[] +=== Filtering doctors from list : `filter-doctor` +Shows a list of all doctors in the HealthBook. + +Format: `filter-doctor` + +[WARNING] +`list` command has to be called after `filter-doctor` is called before proceeding +with other command operations +// end::filterdoctor[] -Deletes the specified person from the address book. + -Format: `delete INDEX` +// tag::addappointment[] +=== Booking an appointment: `add-appointment` +Book a doctor's appointment for the patient. An ID of this appointment will be generated. + +Format: `add-appointment np/PATIENT_NAME [pp/PATIENT_PHONE] nd/DOCTOR_NAME [pd/DOCTOR_PHONE] d/DATE_TIME` + +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. **** -* 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, ... +* Date and time should be in the format `yyyy-MM-dd HH:mm` **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `add-appointment np/Jane Doe pp/92937272 nd/Amanda Jane pd/94827284 d/2018-12-12 13:00` +* `add-appointment np/John Doe nd/Mary Jane d/2018-10-17 15:00` +// end::addappointment[] -=== Selecting a person : `select` +// tag::deleteappointment[] +=== Deleting an appointment `delete-appointment` +Delete a doctor's appointment for the patient by its ID. + +Format: `delete-appointment APPOINTMENT_ID` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +**** +* Only able to delete upcoming appointments +**** + +Examples: + +* `delete-appointment 10001` +// end::deleteappointment[] + +// tag::completeappointment[] +=== Completing an appointment `complete-appointment` +Complete an appointment that has already passed. + +Format: `complete-appointment APPOINTMENT_ID` **** -* Selects the person and loads the Google search page 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, ...` +* Only able to complete past appointments +* Not allowed to complete future appointments **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `complete-appointment 10001` +// end::completeappointment[] -=== Listing entered commands : `history` +// tag::addprescription[] +=== Adding prescriptions to a patient's appointment: `add-prescription` +Adds a prescription to the patient's appointment with details of medicine name, dosage and number of times to consume +in a day. + +Format: `add-prescription APPOINTMENT_ID pn/MEDICINE_NAME pd/DOSAGE pc/CONSUMPTION_PER_DAY` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Examples: -[NOTE] -==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +* `add-prescription 10001 pn/Paracetamol pd/2 pc/3` + +**** +* Each prescription is tied to an appointment. +* There cannot be a duplicate of drug name for that particular appointment. +* Drug that patient is allergic to cannot be added +**** -// tag::undoredo[] -=== Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +[TIP] +After adding the prescription, all commands that requires the prescription's medicine name are non-case sensitive. +// end::addprescription[] + +// tag::deleteprescription[] +=== Deleting prescriptions of a patient's appointment: `delete-prescription` +Delete a prescription of the patient's appointment by appointment ID and medicine name. + +Format: `delete-prescription APPOINTMENT_ID pn/MEDICINE_NAME` + +Examples: + +* `delete-prescription 10001 pn/Paracetamol` +// end::deleteprescription[] + +// tag::addmedicalhistory[] +=== Adding medical condition or allergy to patient: `add-medical-history` +Add a condition or allergy to the patient's medical history. This will then be displayed on the users information page. + +Format: `add-medical-history n/NAME [al/ALLERGIES] [c/CONDITIONS]` [NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +Allergies and conditions should only contain alphanumeric characters and spaces, and they should not be blank. + +Examples: + +* `add-medical-history n/John Doe al/penicillin,milk c/subhealthy,hyperglycemia` +* `add-medical-history n/John Doe al/penicillin,milk` +* `add-medical-history n/John Doe c/subhealthy` + +**** +* Either allergy or condition must be provided. +* Duplicated allergy or condition is not allowed. +* Allergies and conditions should only contain alphanumeric characters and spaces, and they should not be blank. +**** + +[TIP] +After adding the condition/allergy, all commands that requires the allergy/condition are non-case sensitive. +// end::addmedicalhistory[] + +// tag::deletemedicalhistory[] +=== Deleting medical condition or allergy to patient: `delete-medical-history` +Delete a condition or allergy to the patient's medical history. This will then be removed on the patient's information page. + +Format: `delete-medical-history n/NAME [al/ALLERGIES] [c/CONDITIONS]` Examples: -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + +* `delete-medical-history n/John Doe al/penicillin,milk c/subhealthy,hyperglycemia` +* `delete-medical-history n/John Doe al/penicillin,milk` +* `delete-medical-history n/John Doe c/subhealthy` -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. +**** +* Either allergy or condition must be provided. +* Input allergy or condition must exist in the HealthBook. +* Allergy and condition is NOT case sensitive (`EGG` and `egg` are treated as the same). +* Allergies and conditions should only contain alphanumeric characters and spaces, and they should not be blank. +**** +// end::deletemedicalhistory[] -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +// tag::remark[] +=== Adding remark to patient : `remark` +Adds a remark to the patient/doctor's information. Input will override current remark. If input is empty, previous remark will be deleted. + +Format: `remark n/NAME [p/PHONE] r/REMARK` -=== Redoing the previously undone command : `redo` +[NOTE] +Parameter `p/PHONE` is only needed when there are duplicate names in HealthBook, so phone number is needed to differentiate them. -Reverses the most recent `undo` command. + -Format: `redo` Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `remark n/John Doe r/Has chronic heart disease` +* `remark n/Amanda Jane p/84739294 r/` +// end::remark[] -* `delete 1` + -`redo` + -The `redo` command fails as there are no `undo` commands executed previously. +=== Listing entered commands : `history` -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + -// end::undoredo[] +Lists all the commands that you have entered in reverse chronological order. + +Format: `history` + +[NOTE] +==== +Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. +==== === Clearing all entries : `clear` -Clears all entries from the address book. + +Clears all entries from the HealthBook. + Format: `clear` === Exiting the program : `exit` @@ -226,35 +340,66 @@ Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +HealthBook data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +// tag::autocomplete[] +=== Tab to autocomplete commands + +In the command box, user can type the first few characters of the command and tab to autocomplete it. +// end::autocomplete[] -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +// tag::noncasesensitivity[] +=== Non case sensitive parameters +All alphanumeric parameters for commands are not case sensitive except for `Address` and `Email`. +// end::noncasesensitivity[] + +=== Remind patient on their upcoming appointment `[coming soon in v2.0]` +Remind a patient on their upcoming appointment. This will send a message to the patient via an external platform to inform +them about their appointment. + +=== Remind all patient on their upcoming appointment `[coming soon in v2.0]` +Remind all patient will upcoming appointment this week. This will send a message to the patient via an external platform to +inform them about their upcoming appointment. == 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 Address Book 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 HealthBook folder. == Command Summary -* *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` +* *Register patient* `register-patient n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS` + +e.g. `register-patient n/John Doe p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665` +* *Register doctor* `register-patient n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS` + +e.g. `register-doctor n/Mary Jane p/12224444 e/maryjane@example.com a/12, Clementi Rd, 1234665` +* *Delete patient* : `delete-patient n/NAME [p/PHONE]` + +e.g. `delete-patient n/John Doe p/84729292` +* *Delete doctor* : `delete-doctor n/NAME [p/PHONE]` + +e.g. `delete-doctor n/Mary Jane` +* *View patient* : `view-patient n/NAME [p/PHONE]` + +e.g. `view-patient n/John Doe p/84729292` +* *View doctor* : `view-doctor n/NAME [p/PHONE]` + +e.g. `view-doctor n/Mary Jane` * *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` +* *Filter patients* : `filter-patient` +* *Filter doctors* : `filter-doctor` +* *Book appointment* : `add-appointment np/PATIENT_NAME [pp/PATIENT_PHONE] nd/DOCTOR_NAME [pd/PATIENT_PHONE] d/DATE_TIME` + +e.g. `add-appointment np/John Doe nd/Mary Jane pd/93736272 d/2018-10-17 15:00` +* *Delete appointment* : `delete-appointment APPOINTMENT_ID` + +e.g. `delete-appointment 10001` +* *Complete appointment* : `complete-appointment APPOINTMENT_ID` + +e.g. `complete-appointment 10001` +* *Add prescriptions* : `add-prescription APPOINTMENT_ID pn/MEDICINE_NAME pd/DOSAGE pc/CONSUMPTION_PER_DAY` + +e.g. `add-prescription 10001 pn/Paracetamol pd/2 pc/3` +* *Delete prescriptions* : `delete-prescription APPOINTMENT_ID pn/MEDICINE_NAME` + +e.g. `delete-prescription 10001 pn/Paracetamol` +* *Add allergies or conditions* : `add-medical-history n/NAME [al/ALLERGIES] [c/CONDITIONS]` +e.g. `add-medical-history n/John Doe al/penicillin,milk c/subhealthy,hyperglycemia` +* *Delete allergies or conditions* : `delete-medical-history n/NAME [al/ALLERGIES] [c/CONDITIONS]` +e.g. `delete-medical-history n/John Doe al/penicillin,milk c/subhealthy,hyperglycemia` +* *Remark* : `remark n/NAME [p/PHONE] r/REMARK` + +e.g `remark n/John Doe p/84729292 r/Has chronic heart disease` * *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* *Clear* : `clear` +* *Exit* : `exit` diff --git a/docs/UsingAppVeyor.adoc b/docs/UsingAppVeyor.adoc index ba15f7113fc5..fa7368de35c8 100644 --- a/docs/UsingAppVeyor.adoc +++ b/docs/UsingAppVeyor.adoc @@ -86,3 +86,4 @@ https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appv ---- + Copy and paste the asciidoc code to your `README.adoc` file. +Test AppVeyor diff --git a/docs/diagrams/AddAppointmentSequenceDiagram.pptx b/docs/diagrams/AddAppointmentSequenceDiagram.pptx new file mode 100644 index 000000000000..4d4bd10fbfd8 Binary files /dev/null and b/docs/diagrams/AddAppointmentSequenceDiagram.pptx differ diff --git a/docs/diagrams/AddMedicalHistorySequenceDiagram.pptx b/docs/diagrams/AddMedicalHistorySequenceDiagram.pptx new file mode 100644 index 000000000000..be6832f2ece3 Binary files /dev/null and b/docs/diagrams/AddMedicalHistorySequenceDiagram.pptx differ diff --git a/docs/diagrams/AddPrescriptionCommandSequenceDiagram.pptx b/docs/diagrams/AddPrescriptionCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..e3dfc50f0110 Binary files /dev/null and b/docs/diagrams/AddPrescriptionCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/ArchitectureDiagram.pptx b/docs/diagrams/ArchitectureDiagram.pptx index b0e5a9d0ff55..354c99f2e880 100644 Binary files a/docs/diagrams/ArchitectureDiagram.pptx and b/docs/diagrams/ArchitectureDiagram.pptx differ diff --git a/docs/diagrams/CompleteAppointmentSequenceDiagram.pptx b/docs/diagrams/CompleteAppointmentSequenceDiagram.pptx new file mode 100644 index 000000000000..c5073684cc51 Binary files /dev/null and b/docs/diagrams/CompleteAppointmentSequenceDiagram.pptx differ diff --git a/docs/diagrams/DeleteAppointmentSequenceDiagram.pptx b/docs/diagrams/DeleteAppointmentSequenceDiagram.pptx new file mode 100644 index 000000000000..7abaca746a45 Binary files /dev/null and b/docs/diagrams/DeleteAppointmentSequenceDiagram.pptx differ diff --git a/docs/diagrams/DeleteDoctorSequenceDiagram.pptx b/docs/diagrams/DeleteDoctorSequenceDiagram.pptx new file mode 100644 index 000000000000..518e5d5271d5 Binary files /dev/null and b/docs/diagrams/DeleteDoctorSequenceDiagram.pptx differ diff --git a/docs/diagrams/DeleteMedicalHistorySequenceDiagram.pptx b/docs/diagrams/DeleteMedicalHistorySequenceDiagram.pptx new file mode 100644 index 000000000000..5e55d917e1fb Binary files /dev/null and b/docs/diagrams/DeleteMedicalHistorySequenceDiagram.pptx differ diff --git a/docs/diagrams/DeletePatientSequenceDiagram.pptx b/docs/diagrams/DeletePatientSequenceDiagram.pptx new file mode 100644 index 000000000000..8ab821c73abb Binary files /dev/null and b/docs/diagrams/DeletePatientSequenceDiagram.pptx differ diff --git a/docs/diagrams/DeletePrescriptionCommandSequenceDiagram.pptx b/docs/diagrams/DeletePrescriptionCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..6c0b620999f5 Binary files /dev/null and b/docs/diagrams/DeletePrescriptionCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/FilterDoctorSequenceDiagram.pptx b/docs/diagrams/FilterDoctorSequenceDiagram.pptx new file mode 100644 index 000000000000..3e8372523cf6 Binary files /dev/null and b/docs/diagrams/FilterDoctorSequenceDiagram.pptx differ diff --git a/docs/diagrams/FilterPatientSequenceDiagram.pptx b/docs/diagrams/FilterPatientSequenceDiagram.pptx new file mode 100644 index 000000000000..6491c0731567 Binary files /dev/null and b/docs/diagrams/FilterPatientSequenceDiagram.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..fc5649250062 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..aff7728909ff 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..62fe272c91c0 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..37ad6346ce98 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/RegisterDoctorSequenceDiagram.pptx b/docs/diagrams/RegisterDoctorSequenceDiagram.pptx new file mode 100644 index 000000000000..5a622a5b154d Binary files /dev/null and b/docs/diagrams/RegisterDoctorSequenceDiagram.pptx differ diff --git a/docs/diagrams/RegisterPatientSequenceDiagram.pptx b/docs/diagrams/RegisterPatientSequenceDiagram.pptx new file mode 100644 index 000000000000..cf1a4f117537 Binary files /dev/null and b/docs/diagrams/RegisterPatientSequenceDiagram.pptx differ diff --git a/docs/diagrams/RemarkCommandSequenceDiagram.pptx b/docs/diagrams/RemarkCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..049cbaf37984 Binary files /dev/null and b/docs/diagrams/RemarkCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..107aae8cc178 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..4cea6e64cce5 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ViewDoctorSequenceDiagram.pptx b/docs/diagrams/ViewDoctorSequenceDiagram.pptx new file mode 100644 index 000000000000..76b7e17ec6b0 Binary files /dev/null and b/docs/diagrams/ViewDoctorSequenceDiagram.pptx differ diff --git a/docs/diagrams/ViewPatientSequenceDiagram.pptx b/docs/diagrams/ViewPatientSequenceDiagram.pptx new file mode 100644 index 000000000000..4c3e414b95a6 Binary files /dev/null and b/docs/diagrams/ViewPatientSequenceDiagram.pptx differ diff --git a/docs/images/AddAppointmentSequenceDiagram.png b/docs/images/AddAppointmentSequenceDiagram.png new file mode 100644 index 000000000000..1c5a76aaf692 Binary files /dev/null and b/docs/images/AddAppointmentSequenceDiagram.png differ diff --git a/docs/images/AddMedicalHistorySequenceDiagram.png b/docs/images/AddMedicalHistorySequenceDiagram.png new file mode 100644 index 000000000000..a2a2b07ed6a1 Binary files /dev/null and b/docs/images/AddMedicalHistorySequenceDiagram.png differ diff --git a/docs/images/AddPrescriptionSequenceDiagram.png b/docs/images/AddPrescriptionSequenceDiagram.png new file mode 100644 index 000000000000..112d5a3c5b3a Binary files /dev/null and b/docs/images/AddPrescriptionSequenceDiagram.png differ diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index bdc789000f77..b513b7352915 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/CalendarComponentClassDiagram.png b/docs/images/CalendarComponentClassDiagram.png new file mode 100644 index 000000000000..086dd1c1a9cc Binary files /dev/null and b/docs/images/CalendarComponentClassDiagram.png differ diff --git a/docs/images/CompleteAppointmentSequenceDiagram.png b/docs/images/CompleteAppointmentSequenceDiagram.png new file mode 100644 index 000000000000..481605bc755d Binary files /dev/null and b/docs/images/CompleteAppointmentSequenceDiagram.png differ diff --git a/docs/images/DeleteAppointmentSequenceDiagram.png b/docs/images/DeleteAppointmentSequenceDiagram.png new file mode 100644 index 000000000000..95d58419770b Binary files /dev/null and b/docs/images/DeleteAppointmentSequenceDiagram.png differ diff --git a/docs/images/DeleteDoctorSequenceDiagram.png b/docs/images/DeleteDoctorSequenceDiagram.png new file mode 100644 index 000000000000..39a562df5073 Binary files /dev/null and b/docs/images/DeleteDoctorSequenceDiagram.png differ diff --git a/docs/images/DeleteMedicalHistorySequenceDiagram.png b/docs/images/DeleteMedicalHistorySequenceDiagram.png new file mode 100644 index 000000000000..0944e990661f Binary files /dev/null and b/docs/images/DeleteMedicalHistorySequenceDiagram.png differ diff --git a/docs/images/DeletePatientSequenceDiagram.png b/docs/images/DeletePatientSequenceDiagram.png new file mode 100644 index 000000000000..8b5967d54518 Binary files /dev/null and b/docs/images/DeletePatientSequenceDiagram.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png index 0462b9b7be6e..15f12604ea9f 100644 Binary files a/docs/images/DeletePersonSdForLogic.png and b/docs/images/DeletePersonSdForLogic.png differ diff --git a/docs/images/DeletePrescriptionSequenceDiagram.png b/docs/images/DeletePrescriptionSequenceDiagram.png new file mode 100644 index 000000000000..db5029b0c1fa Binary files /dev/null and b/docs/images/DeletePrescriptionSequenceDiagram.png differ diff --git a/docs/images/FilterDoctorSequenceDiagram.png b/docs/images/FilterDoctorSequenceDiagram.png new file mode 100644 index 000000000000..9c62cb667367 Binary files /dev/null and b/docs/images/FilterDoctorSequenceDiagram.png differ diff --git a/docs/images/FilterPatientSequenceDiagram.png b/docs/images/FilterPatientSequenceDiagram.png new file mode 100644 index 000000000000..edca8b63785f Binary files /dev/null and b/docs/images/FilterPatientSequenceDiagram.png differ diff --git a/docs/images/GoogleLoginPage.png b/docs/images/GoogleLoginPage.png new file mode 100644 index 000000000000..b9a23b8e41c2 Binary files /dev/null and b/docs/images/GoogleLoginPage.png differ diff --git a/docs/images/LogicComponentClassDiagram.png b/docs/images/LogicComponentClassDiagram.png new file mode 100644 index 000000000000..a638e1806f12 Binary files /dev/null and b/docs/images/LogicComponentClassDiagram.png differ diff --git a/docs/images/LogicComponentSequenceDiagram.png b/docs/images/LogicComponentSequenceDiagram.png new file mode 100644 index 000000000000..15f12604ea9f Binary files /dev/null and b/docs/images/LogicComponentSequenceDiagram.png differ diff --git a/docs/images/ModelComponentClassDiagram.png b/docs/images/ModelComponentClassDiagram.png new file mode 100644 index 000000000000..8e781eb9b58a Binary files /dev/null and b/docs/images/ModelComponentClassDiagram.png differ diff --git a/docs/images/RegisterDoctorSequenceDiagram.png b/docs/images/RegisterDoctorSequenceDiagram.png new file mode 100644 index 000000000000..432101435beb Binary files /dev/null and b/docs/images/RegisterDoctorSequenceDiagram.png differ diff --git a/docs/images/RegisterPatientSequenceDiagram.png b/docs/images/RegisterPatientSequenceDiagram.png new file mode 100644 index 000000000000..1abc78f3a8a4 Binary files /dev/null and b/docs/images/RegisterPatientSequenceDiagram.png differ diff --git a/docs/images/RemarkCommandSequenceDiagram.png b/docs/images/RemarkCommandSequenceDiagram.png new file mode 100644 index 000000000000..e5d1f2869975 Binary files /dev/null and b/docs/images/RemarkCommandSequenceDiagram.png differ diff --git a/docs/images/RemarkSequenceDiagram.png b/docs/images/RemarkSequenceDiagram.png new file mode 100644 index 000000000000..b5d839445d92 Binary files /dev/null and b/docs/images/RemarkSequenceDiagram.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png index 1e836f10dcd8..89c9ce2c233e 100644 Binary files a/docs/images/SDforDeletePerson.png and b/docs/images/SDforDeletePerson.png differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png index ecec0805d32c..3602bd0197e3 100644 Binary files a/docs/images/SDforDeletePersonEventHandling.png and b/docs/images/SDforDeletePersonEventHandling.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png deleted file mode 100644 index 7a4cd2700cbf..000000000000 Binary files a/docs/images/StorageClassDiagram.png and /dev/null differ diff --git a/docs/images/StorageComponentClassDiagram.png b/docs/images/StorageComponentClassDiagram.png new file mode 100644 index 000000000000..9f268de6244a Binary files /dev/null and b/docs/images/StorageComponentClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..05418b78b665 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 369469ef176e..5ef3d1d3e527 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/ViewDoctorSequenceDiagram.png b/docs/images/ViewDoctorSequenceDiagram.png new file mode 100644 index 000000000000..a292251051e3 Binary files /dev/null and b/docs/images/ViewDoctorSequenceDiagram.png differ diff --git a/docs/images/ViewPatientSequenceDiagram.png b/docs/images/ViewPatientSequenceDiagram.png new file mode 100644 index 000000000000..fcbd92a2b90f Binary files /dev/null and b/docs/images/ViewPatientSequenceDiagram.png differ diff --git a/docs/images/elroyhaw.png b/docs/images/elroyhaw.png new file mode 100644 index 000000000000..0311db703067 Binary files /dev/null and b/docs/images/elroyhaw.png differ diff --git a/docs/images/florafong97.png b/docs/images/florafong97.png new file mode 100644 index 000000000000..04d0cdc83789 Binary files /dev/null and b/docs/images/florafong97.png differ diff --git a/docs/images/giamjuxian.png b/docs/images/giamjuxian.png new file mode 100644 index 000000000000..ee606846b336 Binary files /dev/null and b/docs/images/giamjuxian.png differ diff --git a/docs/images/yangyafei1998.png b/docs/images/yangyafei1998.png new file mode 100644 index 000000000000..cb8b0fd78f5d Binary files /dev/null and b/docs/images/yangyafei1998.png differ diff --git a/docs/team/elroyhaw.adoc b/docs/team/elroyhaw.adoc new file mode 100644 index 000000000000..4baacebcb3b9 --- /dev/null +++ b/docs/team/elroyhaw.adoc @@ -0,0 +1,89 @@ += Elroy Haw - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: HealthBook + +--- + +== Overview + +HealthBook is for clinic receptionist who *prefer to use a desktop app for managing doctors and patients information and appointments*. +It is *optimized for receptionist who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + +== Summary of contributions + +* *Major enhancement*: added the ability to add and delete appointments +** What it does: This allows the user to add new appointments and remove existing appointments that are done in the HealthBook. +** Justification: This feature enables the user to keep track of the upcoming and past appointments in the HealthBook, and updates the doctors' calendars at the same time. +** Highlights: This enhancement is the primary purpose of the HealthBook. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required integration with Google calendar commands. +** Credits: http://github.com/giamjuxian[giamjuxian] for the Google calendar integration + +* *Minor enhancement*: +** Added autocomplete commands on tab feature +** Added commands to view patients/doctors +** Added commands to delete patients/doctors +** Updated UI and events after every command call + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=elroyhaw[Functional code] + +* *Other contributions*: + +** Project management: +*** Managed release `v1.3` on GitHub +*** Setting up AppVeyor, Travis, Coveralls and Netlify for the project +** Implementation of new features: +*** Added `add-appointment` and `delete-appointment` commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/29[#29]) +*** Added `delete-patient` and `delete-doctor` commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/8[#8]) +*** Added `view-patient` and `view-doctor` commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/50[#50], https://github.com/CS2103-AY1819S1-T10-2/main/pull/72[#72]) +*** Wrote test cases for new commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/47[#47], https://github.com/CS2103-AY1819S1-T10-2/main/pull/60[#60], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/77[#77], https://github.com/CS2103-AY1819S1-T10-2/main/pull/78[#78], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/126[#126]) +** Enhancements to existing features: +*** Updated the GUI information panel (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/39[#39], https://github.com/CS2103-AY1819S1-T10-2/main/pull/51[#51]) +*** Updated the GUI to autocomplete commands on tab (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/7[#7]) +** Documentation: +*** Updated User Guide (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/80[#80]) +*** Updated Developer Guide (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/47[#47]) +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103-AY1819S1-T09-4/main/issues/112[1], https://github.com/CS2103-AY1819S1-T09-4/main/issues/110[2]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=deletepatient] + +include::../UserGuide.adoc[tag=deletedoctor] + +include::../UserGuide.adoc[tag=viewpatient] + +include::../UserGuide.adoc[tag=viewdoctor] + +include::../UserGuide.adoc[tag=addappointment] + +include::../UserGuide.adoc[tag=deleteappointment] + +include::../UserGuide.adoc[tag=autocomplete] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=deleteperson] + +include::../DeveloperGuide.adoc[tag=viewperson] + +include::../DeveloperGuide.adoc[tag=addappointment] + +include::../DeveloperGuide.adoc[tag=deleteappointment] + +include::../DeveloperGuide.adoc[tag=glossary] + +include::../DeveloperGuide.adoc[tag=manualtesting] diff --git a/docs/team/florafong97.adoc b/docs/team/florafong97.adoc new file mode 100644 index 000000000000..b82b6636b80a --- /dev/null +++ b/docs/team/florafong97.adoc @@ -0,0 +1,72 @@ += Flora Fong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: HealthBook + +--- + +== Overview + +HealthBook is for clinic receptionist who *prefer to use a desktop app for managing doctors and patients information and appointments*. +It is *optimized for receptionist who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + +== Summary of contributions + +* *Major enhancement*: added *the ability to add/delete a prescription of an appointment* +** What it does: allows the user (receptionist) to add/delete a prescription to add more relevant details to the appointment. +** Justification: This allows the receptionist to keep a record of the prescription related to the appointment to have more information about the visit and patient's conditions. +It will also be useful in the event where patients forgets/is unsure about the prescription and this can be easily accessed. +** Highlights: This enhancement value adds to the HealthBook and is one of the major features of a medical appointment. +It required an in-depth analysis of design alternatives. The implementation too was challenging as it required creating of new models and interaction with all components of the code. + +* *Minor enhancement*: +** Added remark command that adds an unconstrained information snippet to each contact for each person's place card for a quick view of the person's details. +** Made parameters non-case sensitive for all commands i.e `view-patient n/john doe` will view the patient `John Doe` + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=florafong97[Functional code] + +* *Other contributions*: + +** Implementation of new features: +*** Added `remark` command (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/10[#10]) +*** Added `add-prescription` command (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/30[#30]) +*** Added `delete-prescription` command (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/55[#50]) +*** Wrote test cases for new commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/10[#10], https://github.com/CS2103-AY1819S1-T10-2/main/pull/55[#55], https://github.com/CS2103-AY1819S1-T10-2/main/pull/82[#82], https://github.com/CS2103-AY1819S1-T10-2/main/pull/130[#30]) +** Enhancements to existing features: +*** Removed case-sensitivity for parameters for all commands (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/125[#125]) +** Documentation: +*** Updated User Guide +*** Updated Developers guide (Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/21[#21], https://github.com/CS2103-AY1819S1-T10-2/main/pull/165[#165]) +** Community: +*** Reported bugs and suggestions for other teams in the module (Example https://github.com/CS2103-AY1819S1-W12-4/main/issues/127[1], https://github.com/CS2103-AY1819S1-W12-4/main/issues/135[2], https://github.com/CS2103-AY1819S1-W12-4/main/issues/138[3]) + +_{you can add/remove categories in the list above}_ + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=remark] + +include::../UserGuide.adoc[tag=addprescription] + +include::../UserGuide.adoc[tag=deleteprescription] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=remark] + +include::../DeveloperGuide.adoc[tag=addprescription] + +include::../DeveloperGuide.adoc[tag=deleteprescription] + +include::../DeveloperGuide.adoc[tag=usecases] diff --git a/docs/team/giamjuxian.adoc b/docs/team/giamjuxian.adoc new file mode 100644 index 000000000000..b3331b4d269b --- /dev/null +++ b/docs/team/giamjuxian.adoc @@ -0,0 +1,115 @@ += Giam Ju Xian - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: HealthBook + +--- + +== Overview + +HealthBook is for clinic receptionist who *prefer to use a desktop app for managing doctors and patients information and +appointments*. It is *optimized for receptionist who prefer to work with a Command Line Interface* (CLI) while still +having the benefits of a Graphical User Interface (GUI). + +== Summary of contributions + +* *Major enhancement*: Added the command to register both doctors and patients +** What it does: This allows the user to register new doctors and patients into the HealthBook. +** Justification: This feature enables the user to add new doctors and patients into the HealthBook so that further +commands can be called on them. +** Highlights: This enhancement is required for all other commands of the HealthBook to work. It required an in-depth considerations +of the data structure to store the information of patients and doctors. The implementation required a good understanding of +what was already implemented in the previous versions and what is required for further implementations of subsequent commands +and usage. + +* *Major enhancement*: Added the command to complete appointments +** What it does: This allows the user to complete upcoming appointments of patients. +** Justification: This feature enables the user to keep track of the upcoming and past appointments in the HealthBook, +and updates the doctors' calendars at the same time. +** Highlights: This enhancement is the primary purpose of the HealthBook. It required an in-depth analysis of design alternatives. +The implementation too was challenging as it required integration with Google calendar commands. + +* *Major enhancement*: Integrated Google Calendar into HealthBook +** What it does: This allows information of appointments to be presented to doctors in their Google calendar when appointments +are added, completed or deleted. They will be able to review the patient they are seeing as well as time of the appointment easily. +** Justification: This feature allows doctors to receive information about their upcoming appointments in a decentralized + manner. Receptionists can use this integration to pass information to doctors that may not be in the clinic at that point in time. +** Highlights: This enhancement required a good understanding of both integration of Google's API and other commands of +the HealthBook. It required an extensive understanding of integration in Java and the Google's API that is available. +In addition, this implementation required a good analysis of how the Google Calendar can compliment the features in the HealthBook. +** Credits: Google's API https://developers.google.com/resources/api-libraries/documentation/calendar/v3/java/latest/ + +* *Other contributions*: +** Project management: +*** Setting up of Jar files for Release. +*** Assigning issues and datelines after v1.3. + +** Implementation of new features: +*** Added `register-patient` and `register-doctor` commands +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/15[#15]) +*** Added `complete-appointment` command +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/62[#62]) +*** Integrated Google Calendar to `add-appointment`, `delete-appointment` and `complete-appointment` commands +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/54[#54], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/27[#27]) +*** Modified storage to accommodate patients, doctors, appointments and prescriptions. +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/32[#32]) +*** Wrote test cases for new commands +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/56[#56], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/59[#59]) +*** Wrote test cases for changes in models +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/76[#76]) +*** Wrote test cases for changes in storage +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/59[#59]) + +** Enhancements to existing features: +*** Refactored code to change all necessary information of AddressBook to HealthBook +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/152[#152]) +*** Updated default storage and release information +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/81[#81]) + +** Documentation: +*** Updated User Guide +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/131[#131], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/5[#5]) +*** Updated Developer Guide +(Pull requests https://github.com/CS2103-AY1819S1-T10-2/main/pull/131[#131], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/155[#155], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/20[#20], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/35[#35], +https://github.com/CS2103-AY1819S1-T10-2/main/pull/36[#36],) + +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: +https://github.com/CS2103-AY1819S1-T10-2/main/issues/135[1], +https://github.com/CS2103-AY1819S1-T10-2/main/issues/133[2], +https://github.com/CS2103-AY1819S1-T10-2/main/issues/74[3]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=registerpatient] + +include::../UserGuide.adoc[tag=registerdoctor] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=calendarcomponent] + +include::../DeveloperGuide.adoc[tag=modelcomponent] + +include::../DeveloperGuide.adoc[tag=storagecomponent] + +include::../DeveloperGuide.adoc[tag=registerpatient] + +include::../DeveloperGuide.adoc[tag=registerdoctor] diff --git a/docs/team/yangyafei1998.adoc b/docs/team/yangyafei1998.adoc new file mode 100644 index 000000000000..03287793afa5 --- /dev/null +++ b/docs/team/yangyafei1998.adoc @@ -0,0 +1,60 @@ += Yang Yafei - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: HealthBook + +--- + +== Overview + +HealthBook is for clinic receptionist who *prefer to use a desktop app for managing doctors and patients information and appointments*. +It is *optimized for receptionist who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + +== Summary of contributions + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=yangyafei1998[Functional code] + +* *Major enhancement*: added *the ability to add/delete MedicalHistory* +** What it does: This allows the user to add/delete medical history for a certain patient in the HealthBook. +** Justification: This feature enables the user to have knowledge of the medical history which contains allergy and condition of a patient. +** Highlights: This enhancement is an essential feature for HealthBook. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing storage and model. +** Credits: http://github.com/giamjuxian[giamjuxian] for the *Phone* part inside add-medical-history and delete-medical-history commands. + +* *Major enhancement*: added *the ability to filter doctors/patients* +** What it does: This allow the user to filter doctors/patients in the HealthBook. +** Justification: This feature enables the user to have a clear view of patients and doctors. + +* *Other contributions*: + +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103-AY1819S1-T16-1/main/issues/104, https://github.com/CS2103-AY1819S1-T16-1/main/issues/109) + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=addmedicalhistory] + +include::../UserGuide.adoc[tag=deletemedicalhistory] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=addmedicalhistory] + +include::../DeveloperGuide.adoc[tag=deletemedicalhistory] + +include::../DeveloperGuide.adoc[tag=filterdoctor] + +include::../DeveloperGuide.adoc[tag=filterpatient] + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..e3a6642b521e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Nov 07 21:17:07 SGT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/s b/s new file mode 100644 index 000000000000..edb04b965dec --- /dev/null +++ b/s @@ -0,0 +1,407 @@ + .gitattributes | 1 + + .gitignore | 20 + + .travis.yml | 29 + + AboutUs.html | 79 - + ContactUs.html | 15 - + DeveloperGuide.html | 2327 -------------------- + HelpWindow.html | 969 -------- + LICENSE | 21 + + README.adoc | 38 + + UserGuide.html | 969 -------- + UsingAppVeyor.html | 159 -- + UsingCheckstyle.html | 94 - + UsingCoveralls.html | 99 - + UsingGradle.html | 258 --- + UsingNetlify.html | 126 -- + UsingTravis.html | 283 --- + _config.yml | 1 + + appveyor.yml | 18 + + build.gradle | 247 +++ + config/checkstyle/checkstyle.xml | 423 ++++ + config/checkstyle/suppressions.xml | 9 + + config/travis/check-eof-newline.sh | 18 + + config/travis/check-line-endings.sh | 19 + + config/travis/check-trailing-whitespace.sh | 26 + + config/travis/deploy_github_pages.sh | 42 + + config/travis/run-checks.sh | 11 + + copyright.txt | 9 + + docs/AboutUs.adoc | 48 + + docs/ContactUs.adoc | 7 + + docs/DeveloperGuide.adoc | 982 +++++++++ + DummySearchPage.html => docs/DummySearchPage.html | 0 + docs/HelpWindow.adoc | 3 + + docs/UserGuide.adoc | 452 ++++ + docs/UsingAppVeyor.adoc | 89 + + docs/UsingCheckstyle.adoc | 42 + + docs/UsingCoveralls.adoc | 54 + + docs/UsingGradle.adoc | 113 + + docs/UsingNetlify.adoc | 50 + + docs/UsingTravis.adoc | 132 ++ + docs/diagrams/ArchitectureDiagram.pptx | Bin 0 -> 57574 bytes + docs/diagrams/HighLevelSequenceDiagrams.pptx | Bin 0 -> 71479 bytes + docs/diagrams/LogicComponentClassDiagram.pptx | Bin 0 -> 45584 bytes + docs/diagrams/LogicComponentSequenceDiagram.pptx | Bin 0 -> 44254 bytes + .../ModelComponentClassBetterOopDiagram.pptx | Bin 0 -> 42405 bytes + docs/diagrams/ModelComponentClassDiagram.pptx | Bin 0 -> 41337 bytes + docs/diagrams/StorageComponentClassDiagram.pptx | Bin 0 -> 52461 bytes + docs/diagrams/UiComponentClassDiagram.pptx | Bin 0 -> 40744 bytes + docs/diagrams/UndoRedoActivityDiagram.pptx | Bin 0 -> 36901 bytes + .../UndoRedoExecuteUndoStateListDiagram.pptx | Bin 0 -> 39950 bytes + .../UndoRedoNewCommand1StateListDiagram.pptx | Bin 0 -> 38706 bytes + .../UndoRedoNewCommand2StateListDiagram.pptx | Bin 0 -> 39535 bytes + .../UndoRedoNewCommand3StateListDiagram.pptx | Bin 0 -> 39385 bytes + .../UndoRedoNewCommand4StateListDiagram.pptx | Bin 0 -> 40255 bytes + docs/diagrams/UndoRedoSequenceDiagram.pptx | Bin 0 -> 44036 bytes + .../diagrams/UndoRedoStartingStateListDiagram.pptx | Bin 0 -> 32425 bytes + {images => docs/images}/Architecture.png | Bin + {images => docs/images}/DeletePersonSdForLogic.png | Bin + {images => docs/images}/LogicClassDiagram.png | Bin + .../images}/ModelClassBetterOopDiagram.png | Bin + {images => docs/images}/ModelClassDiagram.png | Bin + {images => docs/images}/SDforDeletePerson.png | Bin + .../images}/SDforDeletePersonEventHandling.png | Bin + {images => docs/images}/SeEduLogo.png | Bin + {images => docs/images}/StorageClassDiagram.png | Bin + {images => docs/images}/Ui.png | Bin + {images => docs/images}/UiClassDiagram.png | Bin + .../images}/UndoRedoActivityDiagram.png | Bin + .../UndoRedoExecuteUndoStateListDiagram.png | Bin + .../UndoRedoNewCommand1StateListDiagram.png | Bin + .../UndoRedoNewCommand2StateListDiagram.png | Bin + .../UndoRedoNewCommand3StateListDiagram.png | Bin + .../UndoRedoNewCommand4StateListDiagram.png | Bin + .../images}/UndoRedoSequenceDiagram.png | Bin + .../images}/UndoRedoStartingStateListDiagram.png | Bin + {images => docs/images}/YangYafei1998.png | Bin + {images => docs/images}/appveyor/add-project-1.png | Bin + {images => docs/images}/appveyor/add-project-2.png | Bin + {images => docs/images}/appveyor/add-project-3.png | Bin + {images => docs/images}/appveyor/ci-log.png | Bin + {images => docs/images}/appveyor/ci-pending.png | Bin + {images => docs/images}/appveyor/login.png | Bin + .../images}/appveyor/project-settings-1.png | Bin + .../images}/appveyor/project-settings-2.png | Bin + .../images}/appveyor/project-settings-3.png | Bin + {images => docs/images}/build_pending.png | Bin + .../images}/checkstyle-idea-configuration.png | Bin + .../images}/checkstyle-idea-scan-scope.png | Bin + {images => docs/images}/chrome_save_as_pdf.png | Bin + {images => docs/images}/coveralls/badge_repo.png | Bin + .../images}/coveralls/coverage_asciidoc_code.png | Bin + .../images}/coveralls/coverage_report.png | Bin + .../images}/coveralls/disable_comments.png | Bin + .../images}/coveralls/flick_repository_switch.png | Bin + .../images}/coveralls/github_settings.png | Bin + {images => docs/images}/coveralls/sync_repos.png | Bin + {images => docs/images}/damithc.jpg | Bin + {images => docs/images}/elroyhaw.png | Bin + .../images}/flick_repository_switch.png | Bin + {images => docs/images}/florafong97.png | Bin + {images => docs/images}/generate_token.png | Bin + .../images}/getting-started-ui-result-after.png | Bin + .../images}/getting-started-ui-result-before.png | Bin + .../images}/getting-started-ui-status-after.png | Bin + .../images}/getting-started-ui-status-before.png | Bin + .../images}/getting-started-ui-tag-after.png | Bin + .../images}/getting-started-ui-tag-before.png | Bin + {images => docs/images}/giamjuxian.png | Bin + {images => docs/images}/github_repo_settings.png | Bin + {images => docs/images}/grant_access.png | Bin + {images => docs/images}/lejolly.jpg | Bin + {images => docs/images}/m133225.jpg | Bin + .../images}/netlify/change_site_name.png | Bin + .../images}/netlify/grant_or_request_access.png | Bin + .../images}/netlify/netlify_details.png | Bin + {images => docs/images}/netlify/temp_site_name.png | Bin + {images => docs/images}/request_access.png | Bin + {images => docs/images}/review_and_add.png | Bin + {images => docs/images}/signing_in.png | Bin + {images => docs/images}/travis_add_token.png | Bin + {images => docs/images}/travis_build.png | Bin + {images => docs/images}/yijinl.jpg | Bin + {images => docs/images}/yl_coder.jpg | Bin + docs/index.adoc | 2 + + {stylesheets => docs/stylesheets}/asciidoctor.css | 0 + {stylesheets => docs/stylesheets}/gh-pages.css | 0 + docs/team/johndoe.adoc | 72 + + docs/templates/LICENSE | 24 + + docs/templates/_footer.html.slim | 12 + + docs/templates/_footnotes.html.slim | 7 + + docs/templates/_header.html.slim | 76 + + docs/templates/_toc.html.slim | 5 + + docs/templates/document.html.slim | 29 + + docs/templates/helpers.rb | 300 +++ + gradle.properties | 2 + + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes + gradle/wrapper/gradle-wrapper.properties | 5 + + gradlew | 172 ++ + gradlew.bat | 84 + + index.html | 83 - + src/main/java/seedu/address/AppParameters.java | 64 + + src/main/java/seedu/address/MainApp.java | 208 ++ + .../address/commons/core/ComponentManager.java | 28 + + .../java/seedu/address/commons/core/Config.java | 74 + + .../seedu/address/commons/core/EventsCenter.java | 45 + + .../seedu/address/commons/core/GuiSettings.java | 73 + + .../seedu/address/commons/core/LogsCenter.java | 128 ++ + .../java/seedu/address/commons/core/Messages.java | 13 + + .../java/seedu/address/commons/core/Version.java | 113 + + .../seedu/address/commons/core/index/Index.java | 54 + + .../seedu/address/commons/events/BaseEvent.java | 16 + + .../events/model/AddressBookChangedEvent.java | 19 + + .../events/storage/DataSavingExceptionEvent.java | 21 + + .../commons/events/ui/ExitAppRequestEvent.java | 14 + + .../commons/events/ui/JumpToListRequestEvent.java | 22 + + .../commons/events/ui/NewResultAvailableEvent.java | 21 + + .../ui/PersonPanelSelectionChangedEvent.java | 26 + + .../commons/events/ui/ShowHelpRequestEvent.java | 15 + + .../exceptions/DataConversionException.java | 11 + + .../commons/exceptions/IllegalValueException.java | 21 + + .../java/seedu/address/commons/util/AppUtil.java | 39 + + .../seedu/address/commons/util/CollectionUtil.java | 35 + + .../seedu/address/commons/util/ConfigUtil.java | 23 + + .../java/seedu/address/commons/util/FileUtil.java | 83 + + .../java/seedu/address/commons/util/JsonUtil.java | 143 ++ + .../seedu/address/commons/util/StringUtil.java | 68 + + .../java/seedu/address/commons/util/XmlUtil.java | 71 + + .../java/seedu/address/logic/CommandHistory.java | 58 + + .../seedu/address/logic/ListElementPointer.java | 111 + + src/main/java/seedu/address/logic/Logic.java | 27 + + .../java/seedu/address/logic/LogicManager.java | 52 + + .../logic/commands/AddAppointmentCommand.java | 91 + + .../seedu/address/logic/commands/AddCommand.java | 69 + + .../seedu/address/logic/commands/ClearCommand.java | 25 + + .../java/seedu/address/logic/commands/Command.java | 22 + + .../address/logic/commands/CommandResult.java | 16 + + .../address/logic/commands/DeleteCommand.java | 66 + + .../logic/commands/DeleteDoctorCommand.java | 31 + + .../logic/commands/DeletePatientCommand.java | 31 + + .../seedu/address/logic/commands/EditCommand.java | 230 ++ + .../seedu/address/logic/commands/ExitCommand.java | 23 + + .../seedu/address/logic/commands/FindCommand.java | 43 + + .../seedu/address/logic/commands/HelpCommand.java | 25 + + .../address/logic/commands/HistoryCommand.java | 33 + + .../seedu/address/logic/commands/ListCommand.java | 25 + + .../seedu/address/logic/commands/RedoCommand.java | 31 + + .../logic/commands/RegisterDoctorCommand.java | 65 + + .../logic/commands/RegisterPatientCommand.java | 65 + + .../address/logic/commands/RemarkCommand.java | 87 + + .../address/logic/commands/SelectCommand.java | 57 + + .../seedu/address/logic/commands/UndoCommand.java | 31 + + .../commands/exceptions/CommandException.java | 17 + + .../logic/parser/AddAppointmentCommandParser.java | 46 + + .../address/logic/parser/AddCommandParser.java | 62 + + .../address/logic/parser/AddressBookParser.java | 111 + + .../address/logic/parser/ArgumentMultimap.java | 60 + + .../address/logic/parser/ArgumentTokenizer.java | 148 ++ + .../java/seedu/address/logic/parser/CliSyntax.java | 19 + + .../logic/parser/DeleteDoctorCommandParser.java | 45 + + .../logic/parser/DeletePatientCommandParser.java | 45 + + .../address/logic/parser/EditCommandParser.java | 82 + + .../address/logic/parser/FindCommandParser.java | 33 + + .../java/seedu/address/logic/parser/Parser.java | 16 + + .../seedu/address/logic/parser/ParserUtil.java | 146 ++ + .../java/seedu/address/logic/parser/Prefix.java | 39 + + .../logic/parser/RegisterDoctorCommandParser.java | 65 + + .../logic/parser/RegisterPatientCommandParser.java | 65 + + .../address/logic/parser/RemarkCommandParser.java | 39 + + .../address/logic/parser/SelectCommandParser.java | 28 + + .../logic/parser/exceptions/ParseException.java | 17 + + src/main/java/seedu/address/model/AddressBook.java | 131 ++ + src/main/java/seedu/address/model/Model.java | 94 + + .../java/seedu/address/model/ModelManager.java | 162 ++ + .../seedu/address/model/ReadOnlyAddressBook.java | 17 + + src/main/java/seedu/address/model/UserPrefs.java | 69 + + .../seedu/address/model/VersionedAddressBook.java | 109 + + .../address/model/appointment/Appointment.java | 44 + + .../address/model/appointment/Prescription.java | 38 + + .../seedu/address/model/appointment/Status.java | 8 + + .../java/seedu/address/model/doctor/Doctor.java | 59 + + .../address/model/patient/MedicalHistory.java | 33 + + .../java/seedu/address/model/patient/Patient.java | 83 + + .../java/seedu/address/model/person/Address.java | 59 + + .../java/seedu/address/model/person/Email.java | 67 + + src/main/java/seedu/address/model/person/Name.java | 59 + + .../person/NameContainsKeywordsPredicate.java | 31 + + .../java/seedu/address/model/person/Person.java | 129 ++ + .../java/seedu/address/model/person/Phone.java | 53 + + .../java/seedu/address/model/person/Remark.java | 37 + + .../address/model/person/UniquePersonList.java | 135 ++ + .../exceptions/DuplicatePersonException.java | 11 + + .../person/exceptions/PersonNotFoundException.java | 6 + + src/main/java/seedu/address/model/tag/Tag.java | 54 + + .../seedu/address/model/util/SampleDataUtil.java | 65 + + .../seedu/address/storage/AddressBookStorage.java | 45 + + .../address/storage/JsonUserPrefsStorage.java | 46 + + src/main/java/seedu/address/storage/Storage.java | 39 + + .../java/seedu/address/storage/StorageManager.java | 93 + + .../seedu/address/storage/UserPrefsStorage.java | 35 + + .../seedu/address/storage/XmlAdaptedPerson.java | 148 ++ + .../java/seedu/address/storage/XmlAdaptedTag.java | 62 + + .../address/storage/XmlAddressBookStorage.java | 80 + + .../java/seedu/address/storage/XmlFileStorage.java | 39 + + .../storage/XmlSerializableAddressBook.java | 71 + + src/main/java/seedu/address/ui/BrowserPanel.java | 72 + + src/main/java/seedu/address/ui/CommandBox.java | 174 ++ + src/main/java/seedu/address/ui/HelpWindow.java | 78 + + src/main/java/seedu/address/ui/MainWindow.java | 203 ++ + src/main/java/seedu/address/ui/PersonCard.java | 85 + + .../java/seedu/address/ui/PersonListPanel.java | 83 + + src/main/java/seedu/address/ui/ResultDisplay.java | 41 + + .../java/seedu/address/ui/StatusBarFooter.java | 83 + + src/main/java/seedu/address/ui/Ui.java | 16 + + src/main/java/seedu/address/ui/UiManager.java | 120 + + src/main/java/seedu/address/ui/UiPart.java | 106 + + src/main/resources/images/address_book_32.png | Bin 0 -> 4214 bytes + src/main/resources/images/calendar.png | Bin 0 -> 1215 bytes + src/main/resources/images/clock.png | Bin 0 -> 13751 bytes + src/main/resources/images/fail.png | Bin 0 -> 20783 bytes + src/main/resources/images/help_icon.png | Bin 0 -> 31256 bytes + src/main/resources/images/info_icon.png | Bin 0 -> 5424 bytes + src/main/resources/view/BrowserPanel.fxml | 64 + + src/main/resources/view/CommandBox.fxml | 9 + + src/main/resources/view/DarkTheme.css | 351 +++ + src/main/resources/view/Extensions.css | 20 + + src/main/resources/view/HelpWindow.fxml | 18 + + src/main/resources/view/MainWindow.fxml | 68 + + src/main/resources/view/PersonListCard.fxml | 37 + + src/main/resources/view/PersonListPanel.fxml | 8 + + src/main/resources/view/ResultDisplay.fxml | 9 + + src/main/resources/view/StatusBarFooter.fxml | 14 + + src/main/resources/view/default.html | 9 + + src/test/data/ConfigUtilTest/EmptyConfig.json | 3 + + .../data/ConfigUtilTest/ExtraValuesConfig.json | 6 + + .../data/ConfigUtilTest/NotJsonFormatConfig.json | 1 + + src/test/data/ConfigUtilTest/TypicalConfig.json | 5 + + .../JsonUserPrefsStorageTest/EmptyUserPrefs.json | 3 + + .../ExtraValuesUserPref.json | 13 + + .../NotJsonFormatUserPrefs.json | 1 + + .../JsonUserPrefsStorageTest/TypicalUserPref.json | 11 + + .../NotXmlFormatAddressBook.xml | 1 + + .../invalidAndValidPersonAddressBook.xml | 19 + + .../invalidPersonAddressBook.xml | 10 + + .../duplicatePersonAddressBook.xml | 22 + + .../invalidPersonAddressBook.xml | 10 + + .../typicalPersonsHealthBook.xml | 57 + + src/test/data/XmlUtilTest/empty.xml | 0 + src/test/data/XmlUtilTest/invalidPersonField.xml | 10 + + src/test/data/XmlUtilTest/missingPersonField.xml | 9 + + src/test/data/XmlUtilTest/tempAddressBook.xml | 12 + + src/test/data/XmlUtilTest/validAddressBook.xml | 66 + + src/test/data/XmlUtilTest/validPerson.xml | 9 + + src/test/java/guitests/GuiRobot.java | 119 + + .../guitests/guihandles/AlertDialogHandle.java | 32 + + .../guitests/guihandles/BrowserPanelHandle.java | 64 + + .../java/guitests/guihandles/CommandBoxHandle.java | 42 + + .../java/guitests/guihandles/HelpWindowHandle.java | 34 + + .../java/guitests/guihandles/MainMenuHandle.java | 39 + + .../java/guitests/guihandles/MainWindowHandle.java | 51 + + src/test/java/guitests/guihandles/NodeHandle.java | 44 + + .../java/guitests/guihandles/PersonCardHandle.java | 95 + + .../guitests/guihandles/PersonListPanelHandle.java | 159 ++ + .../guitests/guihandles/ResultDisplayHandle.java | 22 + + src/test/java/guitests/guihandles/StageHandle.java | 63 + + .../guitests/guihandles/StatusBarFooterHandle.java | 72 + + src/test/java/guitests/guihandles/WebViewUtil.java | 31 + + .../exceptions/NodeNotFoundException.java | 9 + + .../exceptions/StageNotFoundException.java | 9 + + src/test/java/seedu/address/AppParametersTest.java | 58 + + src/test/java/seedu/address/TestApp.java | 120 + + .../seedu/address/commons/core/ConfigTest.java | 32 + + .../seedu/address/commons/core/VersionTest.java | 139 ++ + .../address/commons/core/index/IndexTest.java | 92 + + .../seedu/address/commons/util/AppUtilTest.java | 47 + + .../address/commons/util/CollectionUtilTest.java | 117 + + .../seedu/address/commons/util/ConfigUtilTest.java | 131 ++ + .../seedu/address/commons/util/FileUtilTest.java | 24 + + .../seedu/address/commons/util/JsonUtilTest.java | 45 + + .../seedu/address/commons/util/StringUtilTest.java | 159 ++ + .../seedu/address/commons/util/XmlUtilTest.java | 148 ++ + .../seedu/address/logic/CommandHistoryTest.java | 79 + + .../address/logic/ListElementPointerTest.java | 175 ++ + .../java/seedu/address/logic/LogicManagerTest.java | 116 + + .../logic/commands/AddCommandIntegrationTest.java | 49 + + .../address/logic/commands/AddCommandTest.java | 204 ++ + .../address/logic/commands/ClearCommandTest.java | 37 + + .../address/logic/commands/CommandTestUtil.java | 144 ++ + .../logic/commands/DeletePatientCommandTest.java | 16 + + .../address/logic/commands/EditCommandTest.java | 250 +++ + .../logic/commands/EditPersonDescriptorTest.java | 58 + + .../address/logic/commands/ExitCommandTest.java | 30 + + .../address/logic/commands/FindCommandTest.java | 85 + + .../address/logic/commands/HelpCommandTest.java | 30 + + .../address/logic/commands/HistoryCommandTest.java | 35 + + .../address/logic/commands/ListCommandTest.java | 41 + + .../address/logic/commands/RedoCommandTest.java | 49 + + .../address/logic/commands/RemarkCommandTest.java | 206 ++ + .../address/logic/commands/SelectCommandTest.java | 118 + + .../address/logic/commands/UndoCommandTest.java | 45 + + .../address/logic/parser/AddCommandParserTest.java | 141 ++ + .../logic/parser/AddressBookParserTest.java | 147 ++ + .../logic/parser/ArgumentTokenizerTest.java | 150 ++ + .../logic/parser/CommandParserTestUtil.java | 38 + + .../logic/parser/EditCommandParserTest.java | 211 ++ + .../logic/parser/FindCommandParserTest.java | 34 + + .../seedu/address/logic/parser/ParserUtilTest.java | 208 ++ + .../logic/parser/RemarkCommandParserTest.java | 62 + + .../logic/parser/SelectCommandParserTest.java | 29 + + .../java/seedu/address/model/AddressBookTest.java | 110 + + .../java/seedu/address/model/ModelManagerTest.java | 84 + + .../address/model/VersionedAddressBookTest.java | 298 +++ + .../seedu/address/model/person/AddressTest.java | 37 + + .../java/seedu/address/model/person/EmailTest.java | 62 + + .../person/NameContainsKeywordsPredicateTest.java | 75 + + .../java/seedu/address/model/person/NameTest.java | 41 + + .../seedu/address/model/person/PersonTest.java | 99 + + .../java/seedu/address/model/person/PhoneTest.java | 41 + + .../seedu/address/model/person/RemarkTest.java | 31 + + .../address/model/person/UniquePersonListTest.java | 185 ++ + src/test/java/seedu/address/model/tag/TagTest.java | 26 + + .../address/storage/JsonUserPrefsStorageTest.java | 134 ++ + .../seedu/address/storage/StorageManagerTest.java | 103 + + .../address/storage/XmlAdaptedPersonTest.java | 115 + + .../address/storage/XmlAddressBookStorageTest.java | 127 ++ + .../storage/XmlSerializableAddressBookTest.java | 53 + + .../seedu/address/testutil/AddressBookBuilder.java | 34 + + src/test/java/seedu/address/testutil/Assert.java | 53 + + .../testutil/EditPersonDescriptorBuilder.java | 87 + + .../java/seedu/address/testutil/EventsUtil.java | 26 + + .../java/seedu/address/testutil/PersonBuilder.java | 106 + + .../java/seedu/address/testutil/PersonUtil.java | 62 + + .../address/testutil/SerializableTestClass.java | 72 + + src/test/java/seedu/address/testutil/TestUtil.java | 55 + + .../seedu/address/testutil/TypicalIndexes.java | 12 + + .../seedu/address/testutil/TypicalPersons.java | 79 + + .../java/seedu/address/ui/BrowserPanelTest.java | 48 + + src/test/java/seedu/address/ui/CommandBoxTest.java | 157 ++ + src/test/java/seedu/address/ui/GuiUnitTest.java | 38 + + src/test/java/seedu/address/ui/HelpWindowTest.java | 69 + + .../java/seedu/address/ui/MainWindowCloseTest.java | 83 + + src/test/java/seedu/address/ui/PersonCardTest.java | 72 + + .../java/seedu/address/ui/PersonListPanelTest.java | 125 ++ + .../java/seedu/address/ui/ResultDisplayTest.java | 38 + + .../java/seedu/address/ui/StatusBarFooterTest.java | 77 + + src/test/java/seedu/address/ui/TestFxmlObject.java | 35 + + src/test/java/seedu/address/ui/UiPartTest.java | 118 + + .../address/ui/testutil/EventsCollectorRule.java | 86 + + .../seedu/address/ui/testutil/GuiTestAssert.java | 76 + + .../java/seedu/address/ui/testutil/StageRule.java | 34 + + .../java/seedu/address/ui/testutil/UiPartRule.java | 29 + + .../java/systemtests/AddCommandSystemTest.java | 248 +++ + .../java/systemtests/AddressBookSystemTest.java | 292 +++ + .../java/systemtests/ClearCommandSystemTest.java | 101 + + src/test/java/systemtests/ClockRule.java | 57 + + .../java/systemtests/EditCommandSystemTest.java | 299 +++ + src/test/java/systemtests/ErrorDialogGuiTest.java | 34 + + .../java/systemtests/FindCommandSystemTest.java | 185 ++ + .../java/systemtests/HelpCommandSystemTest.java | 100 + + src/test/java/systemtests/ModelHelper.java | 39 + + src/test/java/systemtests/SampleDataTest.java | 51 + + .../java/systemtests/SelectCommandSystemTest.java | 154 ++ + .../java/systemtests/SystemTestSetupHelper.java | 74 + + .../resources/view/UiPartTest/invalidFile.fxml | 1 + + src/test/resources/view/UiPartTest/validFile.fxml | 4 + + .../view/UiPartTest/validFileWithFxRoot.fxml | 6 + + stylesheets/coderay-asciidoctor.css | 89 - + team/johndoe.html | 247 --- + 406 files changed, 21407 insertions(+), 5797 deletions(-) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..fc17daf046d1 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -20,18 +20,18 @@ 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.HealthBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHealthBook; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.HealthBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlHealthBookStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -40,7 +40,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +54,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing HealthBook ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +62,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + HealthBookStorage healthBookStorage = new XmlHealthBookStorage(userPrefs.getHealthBookFilePath()); + storage = new StorageManager(healthBookStorage, userPrefsStorage); initLogging(config); @@ -77,25 +77,25 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s health book and {@code userPrefs}.
+ * The data from the sample health book will be used instead if {@code storage}'s health book is not found, + * or an empty health book will be used instead if errors occur when reading {@code storage}'s health book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional healthBookOptional; + ReadOnlyHealthBook initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + healthBookOptional = storage.readAddressBook(); + if (!healthBookOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample HealthBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = healthBookOptional.orElseGet(SampleDataUtil::getSampleHealthBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty HealthBook"); + initialData = new HealthBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty HealthBook"); + initialData = new HealthBook(); } return new ModelManager(initialData, userPrefs); @@ -159,7 +159,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty HealthBook"); initializedPrefs = new UserPrefs(); } @@ -179,7 +179,7 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting HealthBook " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/calendar/GoogleCalendar.java b/src/main/java/seedu/address/calendar/GoogleCalendar.java new file mode 100644 index 000000000000..4d894b0146ac --- /dev/null +++ b/src/main/java/seedu/address/calendar/GoogleCalendar.java @@ -0,0 +1,42 @@ +package seedu.address.calendar; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.javanet.NetHttpTransport; + +import seedu.address.model.appointment.Appointment; + +/** + * The API of the Calendar component. + */ +public interface GoogleCalendar { + /** + * Creates an authorized Credential object. + * @param httpTransport The network HTTP Transport. + * @return An authorized Credential object. + * @throws IOException If the credentials.json file cannot be found. + */ + public Credential getCredentials(final NetHttpTransport httpTransport, String userName) throws IOException; + + /** + * Register the oAuth token for the respective doctors. + */ + /** */ + public void registerDoctor(String userName) throws IOException, GeneralSecurityException; + + /** + * Adds an appointment event to the primary calendar of the respective doctors. + * This includes an encoded calendar event Id based on the {@code appointment}'s id + */ + /** */ + public void addAppointment(String userName, Appointment appointment) throws IOException, GeneralSecurityException; + + /** + * Delete an appointment event to the primary calendar of the respective doctors. + * Delete using an encoded calendar event Id based on the {@code appointment}'s id + */ + public void deleteAppointment(String userName, Appointment appointment) throws IOException, + GeneralSecurityException; +} diff --git a/src/main/java/seedu/address/calendar/GoogleCalendarManager.java b/src/main/java/seedu/address/calendar/GoogleCalendarManager.java new file mode 100644 index 000000000000..143d4bbfe922 --- /dev/null +++ b/src/main/java/seedu/address/calendar/GoogleCalendarManager.java @@ -0,0 +1,146 @@ +package seedu.address.calendar; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.DateTime; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.calendar.Calendar; +import com.google.api.services.calendar.CalendarScopes; +import com.google.api.services.calendar.model.Event; +import com.google.api.services.calendar.model.EventDateTime; +import com.google.common.base.Charsets; +import com.google.common.io.BaseEncoding; + +import seedu.address.model.appointment.Appointment; + +/** + * Manages all information transaction between doctor's google calendar and HealthBook. + */ +public class GoogleCalendarManager implements GoogleCalendar { + private static final String APPLICATION_NAME = "HealthBook"; + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + private static final String TOKENS_DIRECTORY_PATH = "tokens"; + + /** + * Global instance of the scopes required by this quickstart. + * If modifying these scopes, delete your previously saved tokens/ folder. + */ + private static final List SCOPES = Collections.singletonList(CalendarScopes.CALENDAR_EVENTS); + private static final String CREDENTIALS_FILE_PATH = "credentials.json"; + + @Override + public Credential getCredentials(final NetHttpTransport httpTransport, String userName) throws IOException { + /// Load client secrets. + InputStream in = getClass().getClassLoader().getResourceAsStream(CREDENTIALS_FILE_PATH); + InputStreamReader inStreamReader = new InputStreamReader(in); + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, inStreamReader); + + // Build flow and trigger user authorization request. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + httpTransport, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) + .setAccessType("offline") + .build(); + + LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); + return new AuthorizationCodeInstalledApp(flow, receiver).authorize(userName); + } + + @Override + public void registerDoctor(String userName) throws IOException, GeneralSecurityException { + final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + Calendar service = new Calendar.Builder(httpTransport, JSON_FACTORY, getCredentials(httpTransport, userName)) + .setApplicationName(APPLICATION_NAME) + .build(); + } + + @Override + public void addAppointment(String userName, Appointment appointment) throws IOException, GeneralSecurityException { + final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + Calendar service = new Calendar.Builder(httpTransport, JSON_FACTORY, getCredentials(httpTransport, userName)) + .setApplicationName(APPLICATION_NAME) + .build(); + + // Configure Start Date and Time + LocalDateTime appointmentDateTime = appointment.getDateTime(); + ZonedDateTime zdt = appointmentDateTime.atZone(ZoneId.systemDefault()); + Date startOutput = Date.from(zdt.toInstant()); + Date endOutput = new Date(startOutput.getTime() + 3600 * 1000); + EventDateTime startDateTime = new EventDateTime(); + startDateTime.setDateTime(new DateTime(startOutput)); + EventDateTime endDateTime = new EventDateTime(); + endDateTime.setDateTime(new DateTime(endOutput)); + + // Configure Event Id + String eventId = "healthbook" + Integer.toString(appointment.getAppointmentId()); + String eventIdEncoded = BaseEncoding.base32Hex().encode(eventId.getBytes(Charsets.US_ASCII)).toLowerCase(); + + // Configure Event + Event appointmentToAdd = new Event(); + appointmentToAdd.setStart(startDateTime); + appointmentToAdd.setEnd(endDateTime); + appointmentToAdd.setId(eventIdEncoded); + appointmentToAdd.setDescription("Comments: " + appointment.getComments()); + appointmentToAdd.setSummary("Appointment with " + appointment.getPatient()); + + // Find existing event with same eventId. Deleted eventId is not deleted entirely but cancelled and hidden + Event event = null; + try { + event = service.events().get("primary", eventIdEncoded).execute(); + } catch (GoogleJsonResponseException e) { + if (e.getDetails().getCode() == 400) { // No Event Found + event = null; + } + } + + if (event == null) { + // Insert new Event + service.events().insert("primary", appointmentToAdd).execute(); + } else { + service.events().update("primary", eventIdEncoded, appointmentToAdd).execute(); + } + } + + @Override + public void deleteAppointment(String userName, Appointment appointment) + throws IOException, GeneralSecurityException { + final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + Calendar service = new Calendar.Builder(httpTransport, JSON_FACTORY, getCredentials(httpTransport, userName)) + .setApplicationName(APPLICATION_NAME) + .build(); + + // Configure Event Id + String eventId = "healthbook" + Integer.toString(appointment.getAppointmentId()); + String eventIdEncoded = BaseEncoding.base32Hex().encode(eventId.getBytes(Charsets.US_ASCII)).toLowerCase(); + + try { + // Delete Event + service.events().delete("primary", eventIdEncoded).execute(); + } catch (GoogleJsonResponseException e) { + if (e.getDetails().getCode() == 410) { + return; + } + } + + + } +} diff --git a/src/main/java/seedu/address/calendar/exceptions/AppointmentAlreadyRemovedOnCalendarException.java b/src/main/java/seedu/address/calendar/exceptions/AppointmentAlreadyRemovedOnCalendarException.java new file mode 100644 index 000000000000..647879c3ab87 --- /dev/null +++ b/src/main/java/seedu/address/calendar/exceptions/AppointmentAlreadyRemovedOnCalendarException.java @@ -0,0 +1,11 @@ +package seedu.address.calendar.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class AppointmentAlreadyRemovedOnCalendarException extends RuntimeException { + public AppointmentAlreadyRemovedOnCalendarException() { + super("Appointment already removed on the google calendar."); + } +} diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..f3436efc9017 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Health Book"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 5316a1d87d3e..6999d05662e7 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "healthbook.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/HealthBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/HealthBookChangedEvent.java new file mode 100644 index 000000000000..d5e954e11ca5 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/HealthBookChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyHealthBook; + +/** Indicates the HealthBook in the model has changed*/ +public class HealthBookChangedEvent extends BaseEvent { + + public final ReadOnlyHealthBook data; + + public HealthBookChangedEvent(ReadOnlyHealthBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of persons " + data.getPersonList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java index c5c8b9ce90ed..7b149c9765bc 100644 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java @@ -15,6 +15,10 @@ public PersonPanelSelectionChangedEvent(Person newSelection) { this.newSelection = newSelection; } + public PersonPanelSelectionChangedEvent() { + this.newSelection = null; + } + @Override public String toString() { return getClass().getSimpleName(); diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..b1e4208cdea8 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -3,12 +3,14 @@ import java.util.logging.Logger; import javafx.collections.ObservableList; +import seedu.address.calendar.GoogleCalendar; +import seedu.address.calendar.GoogleCalendarManager; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.HealthBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.person.Person; @@ -21,20 +23,22 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final HealthBookParser healthBookParser; + private final GoogleCalendar googleCalendar; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + healthBookParser = new HealthBookParser(); + googleCalendar = new GoogleCalendarManager(); } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); - return command.execute(model, history); + Command command = healthBookParser.parseCommand(commandText); + return command.execute(model, history, googleCalendar); } finally { history.add(commandText); } diff --git a/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java new file mode 100644 index 000000000000..4cf1651c3b74 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java @@ -0,0 +1,179 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCTOR_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCTOR_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATIENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATIENT_PHONE; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.exceptions.InvalidInputOutputException; +import seedu.address.model.appointment.exceptions.InvalidSecurityAccessException; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Adds a patient's appointment to the health book. + */ +public class AddAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "add-appointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a patient's appointment to the health book.\n" + + "Parameters: " + + PREFIX_PATIENT_NAME + "PATIENT_NAME " + + "[" + PREFIX_PATIENT_PHONE + "PATIENT_PHONE] " + + PREFIX_DOCTOR_NAME + "DOCTOR_NAME " + + "[" + PREFIX_DOCTOR_PHONE + "DOCTOR_PHONE] " + + PREFIX_DATE_TIME + "DATE_TIME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_PATIENT_NAME + "John Doe " + + PREFIX_PATIENT_PHONE + "98765432 " + + PREFIX_DOCTOR_NAME + "Mary Jane " + + PREFIX_DOCTOR_PHONE + "98765434 " + + PREFIX_DATE_TIME + "2018-10-17 15:00 "; + + public static final String MESSAGE_SUCCESS = "New appointment added"; + public static final String MESSAGE_INVALID_PATIENT = "This patient does not exist in the HealthBook"; + public static final String MESSAGE_INVALID_DOCTOR = "This doctor does not exist in the HealthBook"; + public static final String MESSAGE_DOCTOR_CLASH_APPOINTMENT = + "This doctor already have an appointment in that time slot."; + public static final String MESSAGE_PATIENT_CLASH_APPOINTMENT = + "This patient already have an appointment in that time slot."; + public static final String MESSAGE_DUPLICATE_DOCTOR = + "There are multiple doctors with this name. Please enter doctor's number to identify the unique doctor"; + public static final String MESSAGE_DUPLICATE_PATIENT = + "There are multiple patients with this name. Please enter patients's number to identify the unique patient"; + // TODO - add messages for various cases (e.g. conflict in schedule) here when google calendar is up + + private final Name patientName; + private final Name doctorName; + private final LocalDateTime dateTime; + private final Phone patientPhoneNumber; + private final Phone doctorPhoneNumber; + + /** + * Creates an AddAppointmentCommand to add the specified {@code Appointment} + */ + public AddAppointmentCommand(Name patientName, Phone patientPhoneNumber, + Name doctorName, Phone doctorPhoneNumber, + LocalDateTime dateTime) { + requireAllNonNull(patientName, doctorName, dateTime); + this.patientName = patientName; + this.doctorName = doctorName; + this.dateTime = dateTime; + this.patientPhoneNumber = patientPhoneNumber; + this.doctorPhoneNumber = doctorPhoneNumber; + } + + public AddAppointmentCommand(Appointment appointmentToCopy) { + requireNonNull(appointmentToCopy); + this.patientName = new Name(appointmentToCopy.getPatient()); + this.doctorName = new Name(appointmentToCopy.getDoctor()); + this.dateTime = appointmentToCopy.getDateTime(); + this.patientPhoneNumber = null; + this.doctorPhoneNumber = null; + + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Patient patient = null; + Doctor doctor = null; + + for (Person person : lastShownList) { + if (person.getName().equals(patientName) && person instanceof Patient) { + if (patientPhoneNumber != null) { + if (person.getPhone().equals(patientPhoneNumber)) { + patient = (Patient) person; // Name and Phone number unique + } + } else { + if ((patient != null && patient.getName().equals(patientName)) + || (doctor != null && doctor.getName().equals(patientName))) { + throw new CommandException(MESSAGE_DUPLICATE_PATIENT); + } else { + patient = (Patient) person; + } + } + } + if (person.getName().equals(doctorName) && person instanceof Doctor) { + if (doctorPhoneNumber != null) { + if (person.getPhone().equals(doctorPhoneNumber)) { + doctor = (Doctor) person; // Name and Phone number unique + } + } else { + if ((patient != null && patient.getName().equals(doctorName)) + || (doctor != null && doctor.getName().equals(doctorName))) { + throw new CommandException(MESSAGE_DUPLICATE_DOCTOR); + } else { + doctor = (Doctor) person; + } + } + } + } + + if (patient == null) { + throw new CommandException(MESSAGE_INVALID_PATIENT); + } + if (doctor == null) { + throw new CommandException(MESSAGE_INVALID_DOCTOR); + } + + Appointment appointment = new Appointment(model.getAppointmentCounter(), doctor.getName().toString(), + patient.getName().toString(), dateTime); + + try { + googleCalendar.addAppointment(doctor.getName().toString() + doctor.getPhone().toString(), + appointment); + } catch (GeneralSecurityException e) { + throw new InvalidSecurityAccessException(); + } catch (IOException e) { + throw new InvalidInputOutputException(); + } + + if (doctor.hasClashForAppointment(appointment)) { + throw new CommandException(MESSAGE_DOCTOR_CLASH_APPOINTMENT); + } + if (patient.hasClashForAppointment(appointment)) { + throw new CommandException(MESSAGE_PATIENT_CLASH_APPOINTMENT); + } + + doctor.addUpcomingAppointment(appointment); + patient.addUpcomingAppointment(appointment); + model.incrementAppointmentCounter(); + model.addAppointment(appointment); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(patient)); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddAppointmentCommand // instanceof handles nulls + && patientName.equals(((AddAppointmentCommand) other).patientName) + && doctorName.equals(((AddAppointmentCommand) other).doctorName) + && dateTime.equals(((AddAppointmentCommand) other).dateTime)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index d88e831ff1ce..319ec1031a8f 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,12 +1,14 @@ 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 seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -48,7 +50,8 @@ public AddCommand(Person person) { } @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { diff --git a/src/main/java/seedu/address/logic/commands/AddMedicalHistoryCommand.java b/src/main/java/seedu/address/logic/commands/AddMedicalHistoryCommand.java new file mode 100644 index 000000000000..1206be217301 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddMedicalHistoryCommand.java @@ -0,0 +1,210 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.patient.MedicalHistory; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * add medical history for a patient specified by index in healthbook + */ +public class AddMedicalHistoryCommand extends Command { + public static final String COMMAND_WORD = "add-medical-history"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds medical history of a patient to the HealthBook.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ALLERGY + "ALLERGIES (separated by comma)] " + + "[" + PREFIX_CONDITION + "CONDITIONS (separated by comma)] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_ALLERGY + "Penicillin,Milk " + + PREFIX_CONDITION + "subhealthy,Hyperglycemia "; + + public static final String MESSAGE_ADD_MEDICAL_HISTORY_SUCCESS = "New Medical History added for %1$s"; + public static final String MESSAGE_INVALID_ADD_MEDICAL_HISTORY = "This command is only for patients"; + public static final String MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE = " already exists in Medical History"; + public static final String MESSAGE_INVALID_ADD_MEDICAL_HISTORY_NO_INFO = "Please provide valid information"; + public static final String MESSAGE_INVALID_ADD_MEDICAL_HISTORY_NO_MATCH_NAME = + "This patient does not exist in the healthbook"; + public static final String MESSAGE_DUPLICATE_PATIENT = + "There are multiple patients with this name. Please enter patients's number to identify the unique patient"; + public static final String MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE_INPUT = "Duplicate input"; + + private final Name name; + private final Phone phone; + private final ArrayList allergies; + private final ArrayList conditions; + private MedicalHistory medicalHistory = new MedicalHistory(); + + + + /** + * Creates an AddMedicalHistoryCommand to add the specified {@code Person} + */ + public AddMedicalHistoryCommand(Name name, Phone phone, + ArrayList allergies, ArrayList conditions) { + requireNonNull(name); + this.name = name; + this.conditions = conditions; + this.allergies = allergies; + this.phone = phone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + Person personToEdit = null; + boolean personExist = false; + for (Person person : lastShownList) { + if (person.getName().equals(name)) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + personToEdit = person; + personExist = true; + } + } else { + if (personToEdit != null && personToEdit.getName().equals(name)) { + throw new CommandException(MESSAGE_DUPLICATE_PATIENT); + } else { + personToEdit = person; + personExist = true; + } + } + } + } + if (!personExist) { + throw new CommandException(MESSAGE_INVALID_ADD_MEDICAL_HISTORY_NO_MATCH_NAME); + } + if (!(personToEdit.getTags().contains(new Tag("Patient")))) { + throw new CommandException(MESSAGE_INVALID_ADD_MEDICAL_HISTORY); + } + Patient patientToEdit = (Patient) personToEdit; + if (allergies.size() == 0 && conditions.size() == 0) { + throw new CommandException(MESSAGE_INVALID_ADD_MEDICAL_HISTORY_NO_INFO); + } + + if (!(allergies.size() == 0)) { + for (int i = 0; i < allergies.size(); i++) { + for (int j = i + 1; j < allergies.size(); j++) { + if (allergies.get(i).equals(allergies.get(j))) { + throw new CommandException(MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE_INPUT); + } + } + } + if (!(patientToEdit.getMedicalHistory().getAllergies().equals(null))) { + for (int i = 0; i < allergies.size(); i++) { + if (patientToEdit.getMedicalHistory().getAllergies().contains(allergies.get(i))) { + throw new CommandException(allergies.get(i).toString() + + MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE); + } + } + } + } + if (!(conditions.size() == 0)) { + for (int i = 0; i < conditions.size(); i++) { + for (int j = i + 1; j < conditions.size(); j++) { + if (conditions.get(i).equals(conditions.get(j))) { + throw new CommandException(MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE_INPUT); + } + } + } + if (!(patientToEdit.getMedicalHistory().getConditions().equals(null))) { + for (int i = 0; i < conditions.size(); i++) { + if (patientToEdit.getMedicalHistory().getConditions().contains(conditions.get(i))) { + throw new CommandException(conditions.get(i).toString() + + MESSAGE_INVALID_ADD_MEDICAL_HISTORY_DUPLICATE); + } + } + } + } + + + ArrayList newAllergies = new ArrayList(); + ArrayList newConditions = new ArrayList(); + + + if (!(patientToEdit.getMedicalHistory().getAllergies().equals(null))) { + newAllergies.addAll(patientToEdit.getMedicalHistory().getAllergies()); + } + if (allergies.size() != 0) { + newAllergies.addAll(allergies); + } + + if (!(patientToEdit.getMedicalHistory().getConditions().equals(null))) { + newConditions.addAll(patientToEdit.getMedicalHistory().getConditions()); + } + if (conditions.size() != 0) { + newConditions.addAll(conditions); + } + medicalHistory.setAllergies(newAllergies); + medicalHistory.setConditions(newConditions); + + Patient editedPatient = new Patient(patientToEdit.getName(), patientToEdit.getPhone(), patientToEdit.getEmail(), + patientToEdit.getAddress(), patientToEdit.getRemark(), patientToEdit.getTags(), + patientToEdit.getTelegramId(), patientToEdit.getUpcomingAppointments(), + patientToEdit.getPastAppointments()); + editedPatient.setMedicalHistory(medicalHistory); + model.updatePerson(patientToEdit, editedPatient); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(editedPatient)); + return new CommandResult(String.format(MESSAGE_ADD_MEDICAL_HISTORY_SUCCESS, patientToEdit)); + } + + @Override + public boolean equals(Object o) { + if (o == this) { //if same object + return true; + } else if (!(o instanceof AddMedicalHistoryCommand)) { + return false; + } else { + AddMedicalHistoryCommand r = (AddMedicalHistoryCommand) o; + if (!name.equals(r.name)) { + return false; + } + if (allergies.size() != r.allergies.size() || conditions.size() != r.conditions.size()) { + return false; + } + for (int i = 0; i < allergies.size(); i++) { + if (!(allergies.get(i).equals(r.allergies.get(i)))) { + return false; + } + } + for (int i = 0; i < conditions.size(); i++) { + if (!(conditions.get(i).equals(r.conditions.get(i)))) { + return false; + } + } + + return true; + } + + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddPrescriptionCommand.java b/src/main/java/seedu/address/logic/commands/AddPrescriptionCommand.java new file mode 100644 index 000000000000..609dbba2d48b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddPrescriptionCommand.java @@ -0,0 +1,189 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONSUMPTION_PER_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOSAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICINE_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_APPOINTMENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentId; +import seedu.address.model.appointment.Prescription; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; + +/** + * Adds a prescription to an appointment + */ + +public class AddPrescriptionCommand extends Command { + + public static final String COMMAND_WORD = "add-prescription"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a prescription to an appointment. \n" + + "Parameters: " + + "APPOINTMENT_ID " + + PREFIX_MEDICINE_NAME + "MEDICINE_NAME " + + PREFIX_DOSAGE + "DOSAGE " + + PREFIX_CONSUMPTION_PER_DAY + "CONSUMPTION_PER_DAY \n" + + "Example: " + COMMAND_WORD + " " + + "10005 " + + PREFIX_MEDICINE_NAME + "Paracetamol " + + PREFIX_DOSAGE + "2 " + + PREFIX_CONSUMPTION_PER_DAY + "3 "; + + public static final String MESSAGE_SUCCESS = "New Prescription added: %1$s"; + public static final String MESSAGE_DUPLICATE_PRESCRIPTION = "This prescription already exists in the appointment"; + public static final String MESSAGE_APPOINTMENT_DOES_NOT_EXIST = "This appointment does not exist"; + public static final String MESSAGE_PATIENT_ALLERGIC_TO_MEDICINE = "This patient is allergic to %1$s"; + + private final int id; + private final Prescription prescriptionToAdd; + + /** + * Creates an AddPrescriptionCommand to add the specified {@code Person} + */ + public AddPrescriptionCommand(int id, Prescription prescription) { + requireAllNonNull(prescription); + this.id = id; + prescriptionToAdd = prescription; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List appointmentList = model.getFilteredAppointmentList(); + + // check if appointment exists + Appointment appointmentToEdit = null; + for (Appointment appointment : appointmentList) { + if (appointment.getAppointmentId() == id) { + appointmentToEdit = appointment; + break; + } + } + + // if appointment does not exist + if (appointmentToEdit == null) { + throw new CommandException(String.format(MESSAGE_APPOINTMENT_DOES_NOT_EXIST)); + } + + // check if prescription already exists in appointment + if (appointmentToEdit.getPrescriptions().contains(prescriptionToAdd)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_PRESCRIPTION)); + } + + ArrayList allPrescriptions = new ArrayList(); + allPrescriptions.addAll(appointmentToEdit.getPrescriptions()); + + Appointment editedAppointment = new Appointment(new AppointmentId(appointmentToEdit.getAppointmentId()), + appointmentToEdit.getDoctor(), + appointmentToEdit.getPatient(), + appointmentToEdit.getDateTime(), + appointmentToEdit.getStatus(), + appointmentToEdit.getComments(), + allPrescriptions); + editedAppointment.addPrescription(prescriptionToAdd); + model.setAppointment(appointmentToEdit, editedAppointment); + + // checking for patient and doctor + List personList = model.getFilteredPersonList(); + Doctor doctorToEdit = null; + Patient patientToEdit = null; + for (Person person : personList) { + if (person instanceof Doctor) { + if (appointmentToEdit.getDoctor().equals(person.getName().toString())) { + if ((((Doctor) person).hasAppointment(id))) { + doctorToEdit = (Doctor) person; + } + } + } + if (person instanceof Patient) { + if (appointmentToEdit.getPatient().equals(person.getName().toString())) { + if (((Patient) person).hasAppointment(id)) { + patientToEdit = (Patient) person; + } + } + } + if (doctorToEdit != null && patientToEdit != null) { + break; + } + } + + // Doctor only stores upcoming appts while patients store both upcoming and past appt + if (appointmentToEdit.getStatus().equals("UPCOMING")) { + if (patientToEdit == null || doctorToEdit == null) { + throw new CommandException(MESSAGE_APPOINTMENT_DOES_NOT_EXIST); + } + } else { + if (patientToEdit == null) { + throw new CommandException(MESSAGE_APPOINTMENT_DOES_NOT_EXIST); + } + } + + + // check if patient is allergic to medicine + for (Allergy allergy : patientToEdit.getMedicalHistory().getAllergies()) { + String allergyString = allergy.toString(); + if ((allergyString.toLowerCase().equals(prescriptionToAdd.getMedicineName().toString().toLowerCase()))) { + throw new CommandException(String.format(MESSAGE_PATIENT_ALLERGIC_TO_MEDICINE, allergy)); + } + } + + + Patient editedPatient = new Patient(patientToEdit.getName(), patientToEdit.getPhone(), + patientToEdit.getEmail(), patientToEdit.getAddress(), patientToEdit.getRemark(), + patientToEdit.getTags(), patientToEdit.getTelegramId(), patientToEdit.getUpcomingAppointments(), + patientToEdit.getPastAppointments(), patientToEdit.getMedicalHistory()); + editedPatient.setAppointment(appointmentToEdit, editedAppointment); + model.updatePerson(patientToEdit, editedPatient); + + if (doctorToEdit != null) { + Doctor editedDoctor = new Doctor(doctorToEdit.getName(), doctorToEdit.getPhone(), doctorToEdit.getEmail(), + doctorToEdit.getAddress(), doctorToEdit.getRemark(), doctorToEdit.getTags(), + doctorToEdit.getUpcomingAppointments()); + editedDoctor.setAppointment(appointmentToEdit, editedAppointment); + model.updatePerson(doctorToEdit, editedDoctor); + } + + + + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(editedPatient)); + return new CommandResult(String.format(MESSAGE_SUCCESS, prescriptionToAdd.getMedicineName())); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof AddPrescriptionCommand)) { + return false; + } + + AddPrescriptionCommand e = (AddPrescriptionCommand) o; + return id == e.id + && prescriptionToAdd.equals(e.prescriptionToAdd); + + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..7b52749d587c 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,8 +2,11 @@ import static java.util.Objects.requireNonNull; +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; +import seedu.address.model.HealthBook; import seedu.address.model.Model; /** @@ -16,10 +19,12 @@ public class ClearCommand extends Command { @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { requireNonNull(model); - model.resetData(new AddressBook()); + model.resetData(new HealthBook()); model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 34e99d786ec6..2f49d3b741c3 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -1,5 +1,6 @@ package seedu.address.logic.commands; +import seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -17,6 +18,6 @@ public abstract class Command { * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; - + public abstract CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException; } diff --git a/src/main/java/seedu/address/logic/commands/CompleteAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/CompleteAppointmentCommand.java new file mode 100644 index 000000000000..700486ec22a8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CompleteAppointmentCommand.java @@ -0,0 +1,123 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.time.LocalDateTime; +import java.util.Iterator; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.exceptions.InvalidInputOutputException; +import seedu.address.model.appointment.exceptions.InvalidSecurityAccessException; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; + +/** + * Completes an appointment in the health book. + */ +public class CompleteAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "complete-appointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Completes a patient's appointment in the health book. \n" + + "Parameters: " + + "APPOINTMENT_ID \n" + + "Example: " + COMMAND_WORD + " " + + "10001 "; + + public static final String MESSAGE_SUCCESS = "Appointment completed"; + public static final String MESSAGE_INVALID_APPOINTMENT_INDEX = "AppointmentId is invalid"; + public static final String MESSAGE_APPOINTMENT_IN_FUTURE = "Appointment is still in the future"; + + private final int appointmentId; + + /** + * Creates an CompleteAppointmentCommand to add the specified {@code Appointment} + */ + public CompleteAppointmentCommand(int appointmentId) { + requireAllNonNull(appointmentId); + this.appointmentId = appointmentId; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List appointmentList = model.getFilteredAppointmentList(); + List personList = model.getFilteredPersonList(); + Patient patient = null; + Doctor doctor = null; + + Appointment appointment = appointmentList.stream() + .filter(appt -> appt.getAppointmentId() == appointmentId) + .findFirst() + .orElse(null); + + if (appointment == null) { + throw new CommandException(MESSAGE_INVALID_APPOINTMENT_INDEX); + } + + if (appointment.getDateTime().isAfter(LocalDateTime.now())) { + throw new CommandException(MESSAGE_APPOINTMENT_IN_FUTURE); + } + + for (Person person : personList) { + if (person instanceof Patient) { + Iterator iterator = ((Patient) person).getUpcomingAppointments().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getAppointmentId() == appointmentId) { + patient = (Patient) person; + } + } + } + if (person instanceof Doctor) { + Iterator itr = ((Doctor) person).getUpcomingAppointments().iterator(); + while (itr.hasNext()) { + if (itr.next().getAppointmentId() == appointmentId) { + doctor = (Doctor) person; + } + } + } + if (patient != null && doctor != null) { + break; + } + } + + if (patient == null || doctor == null) { + throw new CommandException(MESSAGE_INVALID_APPOINTMENT_INDEX); + } + + try { + googleCalendar.deleteAppointment(doctor.getName().toString() + doctor.getPhone().toString(), + appointment); + } catch (GeneralSecurityException e) { + throw new InvalidSecurityAccessException(); + } catch (IOException e) { + throw new InvalidInputOutputException(); + } + + model.completeAppointment(appointment, patient, doctor); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(patient)); + return new CommandResult(String.format(MESSAGE_SUCCESS, appointment)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CompleteAppointmentCommand // instanceof handles nulls + && (appointmentId == ((CompleteAppointmentCommand) other).appointmentId)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java new file mode 100644 index 000000000000..7c437b087b3f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java @@ -0,0 +1,125 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Iterator; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.Status; +import seedu.address.model.appointment.exceptions.InvalidInputOutputException; +import seedu.address.model.appointment.exceptions.InvalidSecurityAccessException; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; + +/** + * Deletes a patient's appointment to the health book. + */ +public class DeleteAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "delete-appointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes a patient's appointment in the health book. \n" + + "Parameters: " + + "APPOINTMENT_ID \n" + + "Example: " + COMMAND_WORD + " " + + "10001 "; + + public static final String MESSAGE_SUCCESS = "Appointment deleted"; + public static final String MESSAGE_INVALID_APPOINTMENT_INDEX = "AppointmentId is invalid"; + public static final String MESSAGE_COMPLETED_APPOINTMENT = "Completed appointments cannot be deleted"; + + private final int appointmentId; + + /** + * Creates an DeleteAppointmentCommand to add the specified {@code Appointment} + */ + public DeleteAppointmentCommand(int appointmentId) { + requireAllNonNull(appointmentId); + this.appointmentId = appointmentId; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List appointmentList = model.getFilteredAppointmentList(); + List personList = model.getFilteredPersonList(); + Patient patient = null; + Doctor doctor = null; + + Appointment appointment = appointmentList.stream() + .filter(appt -> appt.getAppointmentId() == appointmentId) + .findFirst() + .orElse(null); + + if (appointment == null) { + throw new CommandException(MESSAGE_INVALID_APPOINTMENT_INDEX); + } + + if (appointment.getStatus().equals(Status.COMPLETED)) { + throw new CommandException(MESSAGE_COMPLETED_APPOINTMENT); + } + + for (Person person : personList) { + if (person instanceof Patient) { + Iterator iterator = ((Patient) person).getUpcomingAppointments().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getAppointmentId() == appointmentId) { + patient = (Patient) person; + } + } + } + if (person instanceof Doctor) { + Iterator itr = ((Doctor) person).getUpcomingAppointments().iterator(); + while (itr.hasNext()) { + if (itr.next().getAppointmentId() == appointmentId) { + doctor = (Doctor) person; + } + } + } + if (patient != null && doctor != null) { + break; + } + } + + if (patient == null || doctor == null) { + throw new CommandException(MESSAGE_INVALID_APPOINTMENT_INDEX); + } + + try { + googleCalendar.deleteAppointment(doctor.getName().toString() + doctor.getPhone().toString(), + appointment); + } catch (GeneralSecurityException e) { + throw new InvalidSecurityAccessException(); + } catch (IOException e) { + throw new InvalidInputOutputException(); + } + + patient.deleteAppointment(appointment); + doctor.deleteAppointment(appointment); + model.deleteAppointment(appointment, patient, doctor); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(patient)); + return new CommandResult(String.format(MESSAGE_SUCCESS, appointment)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteAppointmentCommand // instanceof handles nulls + && (appointmentId == ((DeleteAppointmentCommand) other).appointmentId)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteDoctorCommand.java b/src/main/java/seedu/address/logic/commands/DeleteDoctorCommand.java new file mode 100644 index 000000000000..1ae946c744e9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteDoctorCommand.java @@ -0,0 +1,31 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * Deletes a doctor from health book. + */ +public class DeleteDoctorCommand extends DeletePersonCommand { + + public static final String COMMAND_WORD = "delete-doctor"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the doctor identified by name. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe "; + + /** + * Creates an DeleteDoctorCommand to add the specified {@code Doctor} + */ + public DeleteDoctorCommand(Name name, Phone phone) { + super(name, phone, new Tag("Doctor")); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteMedicalHistoryCommand.java b/src/main/java/seedu/address/logic/commands/DeleteMedicalHistoryCommand.java new file mode 100644 index 000000000000..65aef16327ca --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteMedicalHistoryCommand.java @@ -0,0 +1,186 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.patient.MedicalHistory; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * delete medical history for a patient specified by index in healthbook + */ +public class DeleteMedicalHistoryCommand extends Command { + public static final String COMMAND_WORD = "delete-medical-history"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes medical history for a patient in the health book. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ALLERGY + "ALLERGIES (separated by comma)] " + + "[" + PREFIX_CONDITION + "CONDITIONS (separated by comma)] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_ALLERGY + "penicillin,milk " + + PREFIX_CONDITION + "sub-healthy,hyperglycemia "; + + public static final String MESSAGE_DELETE_MEDICAL_HISTORY_SUCCESS = "Medical history deleted for: %1$s"; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_WRONG_TYPE = "This command is only for patients"; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_ALLERGY = "Non Exist Allergy: "; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_CONDITION = "Non Exist Condition: "; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_INFO = "Please provide valid info"; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_MATCH_NAME = + "This patient does not exist in the healthbook"; + public static final String MESSAGE_DUPLICATE_PATIENT = + "There is multiple patients with this name. Please enter patients's number to identify the unique patient"; + public static final String MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_DUPLICATE_INPUT = "Duplicate input"; + + private final Name name; + private final Phone phone; + private final ArrayList allergies; + private final ArrayList conditions; + + /** + * Creates an DeleteMedicalHistoryCommand for the specified {@code Person} + */ + public DeleteMedicalHistoryCommand(Name name, Phone phone, + ArrayList allergies, ArrayList conditions) { + requireNonNull(name); + this.name = name; + this.conditions = conditions; + this.allergies = allergies; + this.phone = phone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + Person personToEdit = null; + boolean personExist = false; + for (Person person : lastShownList) { + if (person.getName().equals(name)) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + personToEdit = person; + personExist = true; + } + } else { + if (personToEdit != null && personToEdit.getName().equals(name)) { + throw new CommandException(MESSAGE_DUPLICATE_PATIENT); + } else { + personToEdit = person; + personExist = true; + } + } + } + } + if (!personExist) { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_MATCH_NAME); + } + + if (!(personToEdit.getTags().contains(new Tag("Patient")))) { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_WRONG_TYPE); + } + Patient patientToEdit = (Patient) personToEdit; + MedicalHistory editedMedicalHistory = new MedicalHistory(patientToEdit.getMedicalHistory()); + if (allergies.size() == 0 && conditions.size() == 0) { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_INFO); + } + if (allergies.size() != 0) { + for (int i = 0; i < allergies.size(); i++) { + for (int j = i + 1; j < allergies.size(); j++) { + if (allergies.get(i).equals(allergies.get(j))) { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_DUPLICATE_INPUT); + } + } + } + for (int index = 0; index < allergies.size(); index++) { + if (editedMedicalHistory.getAllergies().contains(allergies.get(index))) { + editedMedicalHistory.getAllergies().remove(allergies.get(index)); + } else { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_ALLERGY + + allergies.get(index)); + } + } + } + if (conditions.size() != 0) { + for (int i = 0; i < conditions.size(); i++) { + for (int j = i + 1; j < conditions.size(); j++) { + if (conditions.get(i).equals(conditions.get(j))) { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_DUPLICATE_INPUT); + } + } + } + for (int index = 0; index < conditions.size(); index++) { + if (editedMedicalHistory.getConditions().contains(conditions.get(index))) { + editedMedicalHistory.getConditions().remove(conditions.get(index)); + } else { + throw new CommandException(MESSAGE_INVALID_DELETE_MEDICAL_HISTORY_NO_CONDITION + + conditions.get(index)); + } + } + } + Patient editedPatient = new Patient(patientToEdit.getName(), patientToEdit.getPhone(), patientToEdit.getEmail(), + patientToEdit.getAddress(), patientToEdit.getRemark(), patientToEdit.getTags(), + patientToEdit.getTelegramId(), patientToEdit.getUpcomingAppointments(), + patientToEdit.getPastAppointments(), editedMedicalHistory); + model.updatePerson(patientToEdit, editedPatient); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(editedPatient)); + return new CommandResult(String.format(MESSAGE_DELETE_MEDICAL_HISTORY_SUCCESS, patientToEdit)); + } + + @Override + public boolean equals(Object o) { + if (o == this) { //if same object + return true; + } else if (!(o instanceof DeleteMedicalHistoryCommand)) { + return false; + } else { + DeleteMedicalHistoryCommand r = (DeleteMedicalHistoryCommand) o; + if (!name.equals(r.name)) { + return false; + } + if (allergies.size() != r.allergies.size() || conditions.size() != r.conditions.size()) { + return false; + } + for (int i = 0; i < allergies.size(); i++) { + if (!(allergies.get(i).equals(r.allergies.get(i)))) { + return false; + } + } + for (int i = 0; i < conditions.size(); i++) { + if (!(conditions.get(i).equals(r.conditions.get(i)))) { + return false; + } + } + + return true; + } + + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeletePatientCommand.java b/src/main/java/seedu/address/logic/commands/DeletePatientCommand.java new file mode 100644 index 000000000000..6d98b411dffa --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePatientCommand.java @@ -0,0 +1,31 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * Deletes a patient from health book. + */ +public class DeletePatientCommand extends DeletePersonCommand { + + public static final String COMMAND_WORD = "delete-patient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the patient identified by name. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe "; + + /** + * Creates an DeletePatientCommand to add the specified {@code Patient} + */ + public DeletePatientCommand(Name name, Phone phone) { + super(name, phone, new Tag("Patient")); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java b/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java new file mode 100644 index 000000000000..73a2e468bbe1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + +/** + * Deletes a patient from health book. + */ +public class DeletePersonCommand extends Command { + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_INVALID_DELETE_PERSON = "This %1$s does not exist in the HealthBook"; + public static final String MESSAGE_DUPLICATE_DELETE_PERSON = + "There are multiple %1$s with this name. Please enter %2$s's number to identify " + + "the unique %3$s.\n"; + + private final Name name; + private final Phone phone; + private final Tag tag; + + public DeletePersonCommand(Name name, Phone phone, Tag tag) { + this.name = name; + this.tag = tag; + this.phone = phone; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Tag getTag() { + return tag; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Person personToDelete = null; + + for (Person person : lastShownList) { + if (person.getName().equals(name) && person.getTags().contains(tag)) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + personToDelete = person; + } + } else { + if (personToDelete != null && personToDelete.getName().equals(name)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_DELETE_PERSON, + tag.tagName, tag.tagName, tag.tagName)); + } else { + personToDelete = person; + } + } + } + } + if (personToDelete == null) { + throw new CommandException(String.format(MESSAGE_INVALID_DELETE_PERSON, tag.tagName)); + } + + model.deletePerson(personToDelete); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeletePersonCommand // instanceof handles nulls + && name.equals(((DeletePersonCommand) other).name) + && tag.equals(((DeletePersonCommand) other).tag)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeletePrescriptionCommand.java b/src/main/java/seedu/address/logic/commands/DeletePrescriptionCommand.java new file mode 100644 index 000000000000..87ae32640f82 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePrescriptionCommand.java @@ -0,0 +1,176 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICINE_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentId; +import seedu.address.model.appointment.MedicineName; +import seedu.address.model.appointment.Prescription; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; + +/** + * Deletes a prescription from health book + */ +public class DeletePrescriptionCommand extends Command { + + public static final String COMMAND_WORD = "delete-prescription"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a prescription to an appointment. \n" + + "Parameters: " + + "APPOINTMENT ID " + + PREFIX_MEDICINE_NAME + "MEDICINE_NAME \n" + + "Example: " + COMMAND_WORD + " " + + "10005 " + + PREFIX_MEDICINE_NAME + "Paracetamol "; + + public static final String MESSAGE_DELETE_PRESCRIPTION_SUCCESS = "Deleted Prescription: %1$s"; + public static final String MESSAGE_INVALID_DELETE_PRESCRIPTION = + "This prescription does not exist in the HealthBook"; + public static final String MESSAGE_APPOINTMENT_DOES_NOT_EXIST = "This appointment does not exist"; + + private final int id; + private final MedicineName medicineName; + + public DeletePrescriptionCommand(int id, MedicineName medicineName) { + this.id = id; + this.medicineName = medicineName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List appointmentList = model.getFilteredAppointmentList(); + + // check if appointment exists + Appointment appointmentToEdit = null; + for (Appointment appointment : appointmentList) { + if (appointment.getAppointmentId() == id) { + appointmentToEdit = appointment; + break; + } + } + + // appointment does not exist + if (appointmentToEdit == null) { + throw new CommandException(String.format(MESSAGE_APPOINTMENT_DOES_NOT_EXIST)); + } + + // check if prescription exists + Prescription prescriptionToBeDeleted = null; + + for (Prescription prescription : appointmentToEdit.getPrescriptions()) { + if (prescription.getMedicineName().equals(medicineName)) { + prescriptionToBeDeleted = prescription; + break; + } + } + + // prescription does not exist in appointment + if (prescriptionToBeDeleted == null) { + throw new CommandException(String.format(MESSAGE_INVALID_DELETE_PRESCRIPTION)); + } + + // deleting prescription + ArrayList allPrescriptions = new ArrayList(); + allPrescriptions.addAll(appointmentToEdit.getPrescriptions()); + + Appointment editedAppointment = new Appointment(new AppointmentId(appointmentToEdit.getAppointmentId()), + appointmentToEdit.getDoctor(), + appointmentToEdit.getPatient(), + appointmentToEdit.getDateTime(), + appointmentToEdit.getStatus(), + appointmentToEdit.getComments(), + allPrescriptions); + editedAppointment.deletePrescription(medicineName.toString()); + model.setAppointment(appointmentToEdit, editedAppointment); + + //editing persons + List personList = model.getFilteredPersonList(); + Doctor doctorToEdit = null; + Patient patientToEdit = null; + + for (Person person : personList) { + if (person instanceof Doctor) { + if (appointmentToEdit.getDoctor().equals(person.getName().toString())) { + if (((Doctor) person).hasAppointment(id)) { + doctorToEdit = (Doctor) person; + } + } + } + if (person instanceof Patient) { + if (appointmentToEdit.getPatient().equals(person.getName().toString())) { + if (((Patient) person).hasAppointment(id)) { + patientToEdit = (Patient) person; + } + } + } + if (doctorToEdit != null && patientToEdit != null) { + break; + } + } + + // Doctor only stores upcoming appts while patients store both upcoming and past appt + if (appointmentToEdit.getStatus().equals("UPCOMING")) { + if (patientToEdit == null || doctorToEdit == null) { + throw new CommandException(MESSAGE_APPOINTMENT_DOES_NOT_EXIST); + } + } else { + if (patientToEdit == null) { + throw new CommandException(MESSAGE_APPOINTMENT_DOES_NOT_EXIST); + } + } + + Patient editedPatient = new Patient(patientToEdit.getName(), patientToEdit.getPhone(), + patientToEdit.getEmail(), patientToEdit.getAddress(), patientToEdit.getRemark(), + patientToEdit.getTags(), patientToEdit.getTelegramId(), patientToEdit.getUpcomingAppointments(), + patientToEdit.getPastAppointments(), patientToEdit.getMedicalHistory()); + editedPatient.setAppointment(appointmentToEdit, editedAppointment); + model.updatePerson(patientToEdit, editedPatient); + + if (doctorToEdit != null) { + Doctor editedDoctor = new Doctor(doctorToEdit.getName(), doctorToEdit.getPhone(), doctorToEdit.getEmail(), + doctorToEdit.getAddress(), doctorToEdit.getRemark(), doctorToEdit.getTags(), + doctorToEdit.getUpcomingAppointments()); + editedDoctor.setAppointment(appointmentToEdit, editedAppointment); + model.updatePerson(doctorToEdit, editedDoctor); + } + + model.updateFilteredAppointmentList(Model.PREDICATE_SHOW_ALL_APPOINTMENTS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(editedPatient)); + return new CommandResult(String.format(MESSAGE_DELETE_PRESCRIPTION_SUCCESS, + prescriptionToBeDeleted.getMedicineName())); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof DeletePrescriptionCommand)) { + return false; + } + + DeletePrescriptionCommand e = (DeletePrescriptionCommand) o; + return medicineName.equals(e.medicineName); + + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index dc782d8e230f..bb8b3ed34a63 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -14,6 +14,7 @@ import java.util.Optional; import java.util.Set; +import seedu.address.calendar.GoogleCalendar; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; @@ -25,6 +26,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -67,7 +69,8 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { } @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { requireNonNull(model); List lastShownList = model.getFilteredPersonList(); @@ -99,9 +102,10 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Remark updatedRemark = personToEdit.getRemark(); //currently edit does not support remark Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark, updatedTags); } @Override diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index e848fa918964..3771a3605aeb 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -1,5 +1,6 @@ package seedu.address.logic.commands; +import seedu.address.calendar.GoogleCalendar; import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.logic.CommandHistory; @@ -15,7 +16,7 @@ public class ExitCommand extends Command { public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { EventsCenter.getInstance().post(new ExitAppRequestEvent()); return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); } diff --git a/src/main/java/seedu/address/logic/commands/FilterDoctorCommand.java b/src/main/java/seedu/address/logic/commands/FilterDoctorCommand.java new file mode 100644 index 000000000000..5a2b1aa9758b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterDoctorCommand.java @@ -0,0 +1,40 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.tag.TagContainsDoctorPredicate; + + +/** + * Finds and lists all persons in health book who are tagged as patient. + */ +public class FilterDoctorCommand extends Command { + public static final String COMMAND_WORD = "filter-doctor"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons who are tagged as Doctor. \n" + + "Example: " + COMMAND_WORD; + + private final TagContainsDoctorPredicate predicate = new TagContainsDoctorPredicate(); + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterDoctorCommand // instanceof handles nulls + && predicate.equals(((FilterDoctorCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/FilterPatientCommand.java b/src/main/java/seedu/address/logic/commands/FilterPatientCommand.java new file mode 100644 index 000000000000..9f857e92fd63 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterPatientCommand.java @@ -0,0 +1,40 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.tag.TagContainsPatientPredicate; + + +/** + * Finds and lists all persons in health book who are tagged as patient. + */ +public class FilterPatientCommand extends Command { + public static final String COMMAND_WORD = "filter-patient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons who are tagged as Patient. \n" + + "Example: " + COMMAND_WORD; + + private final TagContainsPatientPredicate predicate = new TagContainsPatientPredicate(); + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPatientCommand // instanceof handles nulls + && predicate.equals(((FilterPatientCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index beb178e3a3f5..d2030b5f357e 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; +import seedu.address.calendar.GoogleCalendar; import seedu.address.commons.core.Messages; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; @@ -27,7 +28,7 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { } @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { requireNonNull(model); model.updateFilteredPersonList(predicate); return new CommandResult( diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 66305e95d8f2..e6f5e5b78bf2 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -1,5 +1,6 @@ package seedu.address.logic.commands; +import seedu.address.calendar.GoogleCalendar; import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ShowHelpRequestEvent; import seedu.address.logic.CommandHistory; @@ -18,7 +19,7 @@ public class HelpCommand extends Command { public static final String SHOWING_HELP_MESSAGE = "Opened help window."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { EventsCenter.getInstance().post(new ShowHelpRequestEvent()); return new CommandResult(SHOWING_HELP_MESSAGE); } diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f1541fb57f20..e4a370cc29bc 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; +import seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; @@ -18,7 +19,7 @@ public class HistoryCommand extends Command { public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { requireNonNull(history); List previousCommands = history.getHistory(); diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..9e84e031726e 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; @@ -17,7 +18,7 @@ public class ListCommand extends Command { @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..b4c82e2ac19a 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -17,10 +18,11 @@ public class RedoCommand extends Command { public static final String MESSAGE_FAILURE = "No more commands to redo!"; @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { requireNonNull(model); - if (!model.canRedoAddressBook()) { + if (!model.canRedoAddressBook()) { // Test throw new CommandException(MESSAGE_FAILURE); } diff --git a/src/main/java/seedu/address/logic/commands/RegisterDoctorCommand.java b/src/main/java/seedu/address/logic/commands/RegisterDoctorCommand.java new file mode 100644 index 000000000000..3c004b1454b9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RegisterDoctorCommand.java @@ -0,0 +1,86 @@ +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 java.io.IOException; +import java.security.GeneralSecurityException; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.doctor.Doctor; + +/** + * Adds a doctor to the health book. + */ +public class RegisterDoctorCommand extends Command { + + public static final String COMMAND_WORD = "register-doctor"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Register a doctor to the HealthBook. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Mary Jane " + + PREFIX_PHONE + "98765434 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "; + + public static final String MESSAGE_SUCCESS = "New doctor registered: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = + "There exists a patient/doctor with this name and number in the HealthBook"; + public static final String MESSAGE_WRONG_INPUT = "Wrong input provided. Please try again"; + public static final String MESSAGE_SECURTIY_BREACH = "Unable to create doctor due to security breach"; + + private final Doctor doctorToRegister; + + /** + * Creates an AddDoctorCommand to add the specified {@code Doctor} + */ + public RegisterDoctorCommand(Doctor doctor) { + requireNonNull(doctor); + doctorToRegister = doctor; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + + if (model.hasPerson(doctorToRegister)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + try { + googleCalendar.registerDoctor( + doctorToRegister.getName().toString() + doctorToRegister.getPhone().toString()); + } catch (IOException e) { + throw new CommandException(MESSAGE_WRONG_INPUT); + } catch (GeneralSecurityException e) { + throw new CommandException(MESSAGE_SECURTIY_BREACH); + } + model.addDoctor(doctorToRegister); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, doctorToRegister)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RegisterDoctorCommand // instanceof handles nulls + && doctorToRegister.equals(((RegisterDoctorCommand) other).doctorToRegister)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RegisterPatientCommand.java b/src/main/java/seedu/address/logic/commands/RegisterPatientCommand.java new file mode 100644 index 000000000000..ba626daba28e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RegisterPatientCommand.java @@ -0,0 +1,72 @@ +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 seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Patient; + +/** + * Adds a patient to the health book. + */ +public class RegisterPatientCommand extends Command { + + public static final String COMMAND_WORD = "register-patient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Register a patient to the HealthBook. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "; + + public static final String MESSAGE_SUCCESS = "New patient registered: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = + "There exists a patient/doctor with this name and number in the HealthBook"; + + private final Patient patientToRegister; + + /** + * Creates an RegisterPatientCommand to add the specified {@code Patient} + */ + public RegisterPatientCommand(Patient patient) { + requireNonNull(patient); + patientToRegister = patient; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + + if (model.hasPerson(patientToRegister)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.addPatient(patientToRegister); + model.commitAddressBook(); + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, patientToRegister)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RegisterPatientCommand // instanceof handles nulls + && patientToRegister.equals(((RegisterPatientCommand) other).patientToRegister)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemarkCommand.java b/src/main/java/seedu/address/logic/commands/RemarkCommand.java new file mode 100644 index 000000000000..be1aa2414302 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemarkCommand.java @@ -0,0 +1,136 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; + +/** + * Edits remark of person in HealthBook + */ +public class RemarkCommand extends Command { + + public static final String COMMAND_WORD = "remark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits remark of the person. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE" + "] " + + PREFIX_REMARK + "REMARK \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98371931 " + + PREFIX_REMARK + "Has chronic heart disease "; + + public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to %1$s"; + public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Deleted remark of %1$s"; + public static final String MESSAGE_INVALID_PERSON_FAILURE = "This person does not exist in HealthBook"; + public static final String MESSAGE_DUPLICATE_PERSON = + "There are multiple persons with this name. Please enter person's phone to identify the unique person"; + + + private final Name name; + private final Phone phone; + private final Remark remark; + + /** + * @param name of the person in the filtered person list to edit remark + * @param phone of the person in the filtered person list to edit remark + * @param remark to be updated + */ + + public RemarkCommand(Name name, Phone phone, Remark remark) { + requireAllNonNull(name, remark); + + this.name = name; + this.phone = phone; + this.remark = remark; + } + + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Person personToEdit = null; + + for (Person person : lastShownList) { + if (person.getName().equals(name)) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + personToEdit = person; // name and phone is unique + } + } else { + if (personToEdit != null) { // duplicate found but no phone provided + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } else { + personToEdit = person; + } + } + } + } + + if (personToEdit == null) { + throw new CommandException(MESSAGE_INVALID_PERSON_FAILURE); + } + + if (personToEdit instanceof Patient) { + Patient patientToEdit = (Patient) personToEdit; + Patient editedPatient = new Patient(patientToEdit.getName(), patientToEdit.getPhone(), + patientToEdit.getEmail(), patientToEdit.getAddress(), remark, patientToEdit.getTags(), + patientToEdit.getTelegramId(), patientToEdit.getUpcomingAppointments(), + patientToEdit.getPastAppointments(), patientToEdit.getMedicalHistory()); + model.updatePerson(patientToEdit, editedPatient); + } + + if (personToEdit instanceof Doctor) { + Doctor doctorToEdit = (Doctor) personToEdit; + Doctor editedDoctor = new Doctor(doctorToEdit.getName(), doctorToEdit.getPhone(), + doctorToEdit.getEmail(), doctorToEdit.getAddress(), remark, doctorToEdit.getTags(), + doctorToEdit.getUpcomingAppointments()); + model.updatePerson(doctorToEdit, editedDoctor); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.commitAddressBook(); + + /** + * Remark is deleted if input field is empty + */ + return !(remark.value.isEmpty()) ? new CommandResult(String.format(MESSAGE_ADD_REMARK_SUCCESS, + personToEdit.getName())) + : new CommandResult(String.format(MESSAGE_DELETE_REMARK_SUCCESS, personToEdit.getName())); + } + + @Override + public boolean equals(Object o) { + if (o == this) { //if same object + return true; + } else if (!(o instanceof RemarkCommand)) { + return false; + } else { + RemarkCommand r = (RemarkCommand) o; + boolean optionalPhoneIsEquals = (phone != null) ? phone.equals(r.phone) : true; + return name.equals(r.name) + && optionalPhoneIsEquals + && remark.equals(r.remark); + } + + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index f5e8c1a8722e..fbc5320c0c17 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -4,6 +4,7 @@ import java.util.List; +import seedu.address.calendar.GoogleCalendar; import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; @@ -34,7 +35,8 @@ public SelectCommand(Index targetIndex) { } @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { requireNonNull(model); List filteredPersonList = model.getFilteredPersonList(); diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..85f294924e0c 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.calendar.GoogleCalendar; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -17,7 +18,8 @@ public class UndoCommand extends Command { public static final String MESSAGE_FAILURE = "No more commands to undo!"; @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { requireNonNull(model); if (!model.canUndoAddressBook()) { diff --git a/src/main/java/seedu/address/logic/commands/ViewDoctorCommand.java b/src/main/java/seedu/address/logic/commands/ViewDoctorCommand.java new file mode 100644 index 000000000000..7831e99244cf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewDoctorCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * View a doctor's upcoming appointments. + */ +public class ViewDoctorCommand extends Command { + + public static final String COMMAND_WORD = "view-doctor"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Views the doctor identified by the name.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe "; + + public static final String MESSAGE_SUCCESS = "Viewing Doctor: %1$s"; + public static final String MESSAGE_INVALID_DOCTOR = "This doctor does not exist in the HealthBook"; + public static final String MESSAGE_DUPLICATE_VIEW_DOCTOR = + "There is multiple doctors with this name. Please enter doctor's number to identify the unique doctor."; + + private final Name name; + private final Phone phone; + + public ViewDoctorCommand(Name name, Phone phone) { + this.name = name; + this.phone = phone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List personList = model.getFilteredPersonList(); + Doctor doctorToView = null; + for (Person person : personList) { + if (person.getName().equals(name) && person instanceof Doctor) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + doctorToView = (Doctor) person; + } + } else { + if (doctorToView != null && doctorToView.getName().equals(name)) { + throw new CommandException(MESSAGE_DUPLICATE_VIEW_DOCTOR); + } else { + doctorToView = (Doctor) person; + } + } + } + } + + if (doctorToView == null) { + throw new CommandException(MESSAGE_INVALID_DOCTOR); + } + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(doctorToView)); + return new CommandResult(String.format(MESSAGE_SUCCESS, doctorToView.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewDoctorCommand // instanceof handles nulls + && name.equals(((ViewDoctorCommand) other).name)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewPatientCommand.java b/src/main/java/seedu/address/logic/commands/ViewPatientCommand.java new file mode 100644 index 000000000000..653f48503a20 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewPatientCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.List; + +import seedu.address.calendar.GoogleCalendar; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * View a patient's information. + */ +public class ViewPatientCommand extends Command { + + public static final String COMMAND_WORD = "view-patient"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Views the patient identified by the name.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe "; + + public static final String MESSAGE_SUCCESS = "Viewing Patient: %1$s"; + public static final String MESSAGE_INVALID_PATIENT = "This patient does not exist in the HealthBook"; + public static final String MESSAGE_DUPLICATE_VIEW_PATIENT = + "There is multiple patients with this name. Please enter doctor's number to identify the unique patient."; + + private final Name name; + private final Phone phone; + + public ViewPatientCommand(Name name, Phone phone) { + this.name = name; + this.phone = phone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history, GoogleCalendar googleCalendar) + throws CommandException { + requireNonNull(model); + List personList = model.getFilteredPersonList(); + Patient patientToView = null; + for (Person person : personList) { + if (person.getName().equals(name) && person instanceof Patient) { + if (phone != null) { + if (person.getPhone().equals(phone)) { + patientToView = (Patient) person; + } + } else { + if (patientToView != null && patientToView.getName().equals(name)) { + throw new CommandException(MESSAGE_DUPLICATE_VIEW_PATIENT); + } else { + patientToView = (Patient) person; + } + } + } + } + + if (patientToView == null) { + throw new CommandException(MESSAGE_INVALID_PATIENT); + } + + EventsCenter.getInstance().post(new PersonPanelSelectionChangedEvent(patientToView)); + return new CommandResult(String.format(MESSAGE_SUCCESS, patientToView.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewPatientCommand // instanceof handles nulls + && name.equals(((ViewPatientCommand) other).name)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java new file mode 100644 index 000000000000..3d5b53cac0dd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCTOR_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCTOR_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATIENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATIENT_PHONE; + +import java.time.LocalDateTime; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new AddAppointmentCommand object + */ +public class AddAppointmentCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddAppointmentCommand + * and returns an AddAppointmentCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddAppointmentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PATIENT_NAME, PREFIX_PATIENT_PHONE, + PREFIX_DOCTOR_NAME, PREFIX_DOCTOR_PHONE, PREFIX_DATE_TIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_PATIENT_NAME, PREFIX_DOCTOR_NAME, PREFIX_DATE_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddAppointmentCommand.MESSAGE_USAGE)); + } + + Name patientName = ParserUtil.parseName(argMultimap.getValue(PREFIX_PATIENT_NAME).get()); + Phone patientPhoneNumber = null; + if (argMultimap.getValue(PREFIX_PATIENT_PHONE).isPresent()) { + patientPhoneNumber = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PATIENT_PHONE).get()); + } + Phone doctorPhoneNumber = null; + if (argMultimap.getValue(PREFIX_DOCTOR_PHONE).isPresent()) { + doctorPhoneNumber = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_DOCTOR_PHONE).get()); + } + Name doctorName = ParserUtil.parseName(argMultimap.getValue(PREFIX_DOCTOR_NAME).get()); + LocalDateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE_TIME).get()); + return new AddAppointmentCommand(patientName, patientPhoneNumber, doctorName, doctorPhoneNumber, + dateTime); + } + + /** + * 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/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..791963647b08 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -17,6 +17,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -42,9 +43,10 @@ public AddCommand parse(String args) throws ParseException { 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()); + Remark remark = new Remark(""); //currently add doesn't support remark Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address, remark, tagList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddMedicalHistoryCommandParser.java b/src/main/java/seedu/address/logic/parser/AddMedicalHistoryCommandParser.java new file mode 100644 index 000000000000..6c869829f889 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddMedicalHistoryCommandParser.java @@ -0,0 +1,92 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.patient.Allergy.MESSAGE_ALLERGY_CONSTRAINTS; +import static seedu.address.model.patient.Allergy.isValidAllergy; +import static seedu.address.model.patient.Condition.MESSAGE_CONDITION_CONSTRAINTS; +import static seedu.address.model.patient.Condition.isValidCondition; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddMedicalHistoryCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + + +/** + * Parses input arguments and creates a new AddMedicalHistoryCommand object + */ +public class AddMedicalHistoryCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddMedicalHistoryCommand + * and returns an AddMedicalHistoryCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddMedicalHistoryCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ALLERGY, PREFIX_CONDITION); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddMedicalHistoryCommand.MESSAGE_USAGE)); + } + if (!arePrefixesPresent(argMultimap, PREFIX_ALLERGY) && !arePrefixesPresent(argMultimap, PREFIX_CONDITION)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddMedicalHistoryCommand.MESSAGE_USAGE)); + } + + String nameStr = argMultimap.getValue(PREFIX_NAME).get(); + String allergyString = null; + String conditionString = null; + ArrayList allergies = new ArrayList<>(); + ArrayList conditions = new ArrayList<>(); + Phone phone = null; + if (arePrefixesPresent(argMultimap, PREFIX_ALLERGY)) { + allergyString = argMultimap.getValue(PREFIX_ALLERGY).get(); + ArrayList stringAllergies = new ArrayList<>(Arrays.asList(allergyString.split(","))); + for (int i = 0; i < stringAllergies.size(); i++) { + if (!isValidAllergy(stringAllergies.get(i).trim())) { + throw new ParseException(MESSAGE_ALLERGY_CONSTRAINTS); + } + Allergy allergy = new Allergy(stringAllergies.get(i).trim()); + allergies.add(allergy); + } + } + if (arePrefixesPresent(argMultimap, PREFIX_CONDITION)) { + conditionString = argMultimap.getValue(PREFIX_CONDITION).get(); + ArrayList stringConditions = new ArrayList<>(Arrays.asList(conditionString.split(","))); + for (int i = 0; i < stringConditions.size(); i++) { + if (!isValidCondition(stringConditions.get(i).trim())) { + throw new ParseException(MESSAGE_CONDITION_CONSTRAINTS); + } + Condition condition = new Condition(stringConditions.get(i).trim()); + conditions.add(condition); + } + } + + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + Name name = new Name(nameStr); + + + return new AddMedicalHistoryCommand(name, phone, allergies, conditions); + } + /** + * 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/AddPrescriptionCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPrescriptionCommandParser.java new file mode 100644 index 000000000000..df47df20c598 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddPrescriptionCommandParser.java @@ -0,0 +1,62 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONSUMPTION_PER_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DOSAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICINE_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddPrescriptionCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.appointment.ConsumptionPerDay; +import seedu.address.model.appointment.Dosage; +import seedu.address.model.appointment.MedicineName; +import seedu.address.model.appointment.Prescription; + +/** + * Parses input arguments and creates a new AddPrescriptionCommand object + */ +public class AddPrescriptionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPrescriptionCommand + * and returns an AddPrescriptionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddPrescriptionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MEDICINE_NAME, PREFIX_DOSAGE, + PREFIX_CONSUMPTION_PER_DAY); + + int id; + + try { + id = ParserUtil.parseId(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddPrescriptionCommand.MESSAGE_USAGE), pe); + } + + if (!arePrefixesPresent(argMultimap, PREFIX_MEDICINE_NAME, PREFIX_DOSAGE, + PREFIX_CONSUMPTION_PER_DAY) || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddPrescriptionCommand.MESSAGE_USAGE)); + } + + MedicineName medicineName = ParserUtil.parseMedicineName(argMultimap.getValue(PREFIX_MEDICINE_NAME).get()); + Dosage dosage = ParserUtil.parseDosage(argMultimap.getValue(PREFIX_DOSAGE).get()); + ConsumptionPerDay consumptionPerDay = ParserUtil.parseConsumptionPerDay( + argMultimap.getValue(PREFIX_CONSUMPTION_PER_DAY).get()); + + Prescription prescription = new Prescription(id, medicineName, dosage, consumptionPerDay); + + return new AddPrescriptionCommand(id, prescription); + + } + + 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/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..ccdd8c47a08c 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,18 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_REMARK = new Prefix("r/"); + public static final Prefix PREFIX_ALLERGY = new Prefix("al/"); + public static final Prefix PREFIX_CONDITION = new Prefix("c/"); + public static final Prefix PREFIX_PATIENT_NAME = new Prefix("np/"); + public static final Prefix PREFIX_PATIENT_PHONE = new Prefix("pp/"); + public static final Prefix PREFIX_DOCTOR_NAME = new Prefix("nd/"); + public static final Prefix PREFIX_DOCTOR_PHONE = new Prefix("pd/"); + public static final Prefix PREFIX_DATE_TIME = new Prefix("d/"); + + public static final Prefix PREFIX_INDEX = new Prefix("pi/"); + public static final Prefix PREFIX_MEDICINE_NAME = new Prefix("pn/"); + public static final Prefix PREFIX_DOSAGE = new Prefix("pd/"); + public static final Prefix PREFIX_CONSUMPTION_PER_DAY = new Prefix("pc/"); } diff --git a/src/main/java/seedu/address/logic/parser/CompleteAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/CompleteAppointmentCommandParser.java new file mode 100644 index 000000000000..c73e7e5664c0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CompleteAppointmentCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.CompleteAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new DeleteAppointmentCommand object + */ +public class CompleteAppointmentCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the DeleteAppointmentCommand + * and returns an AddAppointmentCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteAppointmentCommand parse(String args) throws ParseException { + try { + int appointmentId = ParserUtil.parseId(args); + return new CompleteAppointmentCommand(appointmentId); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteAppointmentCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java new file mode 100644 index 000000000000..d038f5d12dbc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DeleteAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new DeleteAppointmentCommand object + */ +public class DeleteAppointmentCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the DeleteAppointmentCommand + * and returns an AddAppointmentCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteAppointmentCommand parse(String args) throws ParseException { + try { + int appointmentId = ParserUtil.parseId(args); + return new DeleteAppointmentCommand(appointmentId); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAppointmentCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 4d1f4bb0e4ec..000000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns an DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteDoctorCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteDoctorCommandParser.java new file mode 100644 index 000000000000..b2c23ff21742 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteDoctorCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteDoctorCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new DeleteDoctorCommand object + */ +public class DeleteDoctorCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteDoctorCommand + * and returns an DeleteDoctorCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteDoctorCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteDoctorCommand.MESSAGE_USAGE)); + } + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = null; + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + return new DeleteDoctorCommand(name, phone); + } + + /** + * 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/DeleteMedicalHistoryCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteMedicalHistoryCommandParser.java new file mode 100644 index 000000000000..c282846319a8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteMedicalHistoryCommandParser.java @@ -0,0 +1,88 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.patient.Allergy.MESSAGE_ALLERGY_CONSTRAINTS; +import static seedu.address.model.patient.Allergy.isValidAllergy; +import static seedu.address.model.patient.Condition.MESSAGE_CONDITION_CONSTRAINTS; +import static seedu.address.model.patient.Condition.isValidCondition; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteMedicalHistoryCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new DeleteMedicalHistoryCommand object + */ +public class DeleteMedicalHistoryCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the DeleteMedicalHistoryCommand + * and returns an DeleteMedicalHistoryCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteMedicalHistoryCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ALLERGY, PREFIX_CONDITION); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteMedicalHistoryCommand.MESSAGE_USAGE)); + } + if (!arePrefixesPresent(argMultimap, PREFIX_ALLERGY) && !arePrefixesPresent(argMultimap, PREFIX_CONDITION)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteMedicalHistoryCommand.MESSAGE_USAGE)); + } + String nameStr = argMultimap.getValue(PREFIX_NAME).get(); + String allergyString = null; + String conditionString = null; + ArrayList allergies = new ArrayList<>(); + ArrayList conditions = new ArrayList<>(); + Phone phone = null; + if (arePrefixesPresent(argMultimap, PREFIX_ALLERGY)) { + allergyString = argMultimap.getValue(PREFIX_ALLERGY).get(); + ArrayList stringAllergies = new ArrayList<>(Arrays.asList(allergyString.split(","))); + for (int i = 0; i < stringAllergies.size(); i++) { + if (!isValidAllergy(stringAllergies.get(i).trim())) { + throw new ParseException(MESSAGE_ALLERGY_CONSTRAINTS); + } + Allergy allergy = new Allergy(stringAllergies.get(i).trim()); + allergies.add(allergy); + } + } + if (arePrefixesPresent(argMultimap, PREFIX_CONDITION)) { + conditionString = argMultimap.getValue(PREFIX_CONDITION).get(); + ArrayList stringConditions = new ArrayList<>(Arrays.asList(conditionString.split(","))); + for (int i = 0; i < stringConditions.size(); i++) { + if (!isValidCondition(stringConditions.get(i).trim())) { + throw new ParseException(MESSAGE_CONDITION_CONSTRAINTS); + } + Condition condition = new Condition(stringConditions.get(i).trim()); + conditions.add(condition); + } + } + + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + Name name = new Name(nameStr); + + return new DeleteMedicalHistoryCommand(name, phone, allergies, conditions); + } + /** + * 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/DeletePatientCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletePatientCommandParser.java new file mode 100644 index 000000000000..4bd7301957c1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeletePatientCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeletePatientCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new DeletePatientCommand object + */ +public class DeletePatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeletePatientCommand + * and returns an DeletePatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePatientCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletePatientCommand.MESSAGE_USAGE)); + } + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = null; + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + return new DeletePatientCommand(name, phone); + } + + /** + * 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/DeletePrescriptionCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletePrescriptionCommandParser.java new file mode 100644 index 000000000000..fb5f50e07685 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeletePrescriptionCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICINE_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeletePrescriptionCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.appointment.MedicineName; + + +/** + * Parses input arguments and creates a new DeletePrescriptionCommand object + */ +public class DeletePrescriptionCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the DeletePrescriptionCommand + * and returns a DeleteAppointmentCommand object for execution + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePrescriptionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(args, PREFIX_MEDICINE_NAME); + + int id; + + try { + id = ParserUtil.parseId(argMultiMap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeletePrescriptionCommand.MESSAGE_USAGE), pe); + } + + if (!arePrefixesPresent(argMultiMap, PREFIX_MEDICINE_NAME) + || argMultiMap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeletePrescriptionCommand.MESSAGE_USAGE)); + } + + MedicineName medicineName = ParserUtil.parseMedicineName(argMultiMap.getValue(PREFIX_MEDICINE_NAME).get()); + + return new DeletePrescriptionCommand(id, medicineName); + } + + + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/HealthBookParser.java similarity index 50% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/HealthBookParser.java index b7d57f5db86a..6547c98a6032 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/HealthBookParser.java @@ -6,25 +6,40 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import seedu.address.logic.commands.AddAppointmentCommand; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddMedicalHistoryCommand; +import seedu.address.logic.commands.AddPrescriptionCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.CompleteAppointmentCommand; +import seedu.address.logic.commands.DeleteAppointmentCommand; +import seedu.address.logic.commands.DeleteDoctorCommand; +import seedu.address.logic.commands.DeleteMedicalHistoryCommand; +import seedu.address.logic.commands.DeletePatientCommand; +import seedu.address.logic.commands.DeletePrescriptionCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FilterDoctorCommand; +import seedu.address.logic.commands.FilterPatientCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RegisterDoctorCommand; +import seedu.address.logic.commands.RegisterPatientCommand; +import seedu.address.logic.commands.RemarkCommand; import seedu.address.logic.commands.SelectCommand; import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.ViewDoctorCommand; +import seedu.address.logic.commands.ViewPatientCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses user input. */ -public class AddressBookParser { +public class HealthBookParser { /** * Used for initial separation of command word and args. @@ -47,18 +62,32 @@ public Command parseCommand(String userInput) throws ParseException { final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); + case RegisterDoctorCommand.COMMAND_WORD: + return new RegisterDoctorCommandParser().parse(arguments); + + case RegisterPatientCommand.COMMAND_WORD: + return new RegisterPatientCommandParser().parse(arguments); + + case AddPrescriptionCommand.COMMAND_WORD: + return new AddPrescriptionCommandParser().parse(arguments); + + case DeletePrescriptionCommand.COMMAND_WORD: + return new DeletePrescriptionCommandParser().parse(arguments); + case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); case SelectCommand.COMMAND_WORD: return new SelectCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); + case DeleteDoctorCommand.COMMAND_WORD: + return new DeleteDoctorCommandParser().parse(arguments); + + case DeletePatientCommand.COMMAND_WORD: + return new DeletePatientCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: return new ClearCommand(); @@ -84,6 +113,36 @@ public Command parseCommand(String userInput) throws ParseException { case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case RemarkCommand.COMMAND_WORD: + return new RemarkCommandParser().parse(arguments); + + case AddMedicalHistoryCommand.COMMAND_WORD: + return new AddMedicalHistoryCommandParser().parse(arguments); + + case DeleteMedicalHistoryCommand.COMMAND_WORD: + return new DeleteMedicalHistoryCommandParser().parse(arguments); + + case FilterPatientCommand.COMMAND_WORD: + return new FilterPatientCommand(); + + case FilterDoctorCommand.COMMAND_WORD: + return new FilterDoctorCommand(); + + case AddAppointmentCommand.COMMAND_WORD: + return new AddAppointmentCommandParser().parse(arguments); + + case DeleteAppointmentCommand.COMMAND_WORD: + return new DeleteAppointmentCommandParser().parse(arguments); + + case CompleteAppointmentCommand.COMMAND_WORD: + return new CompleteAppointmentCommandParser().parse(arguments); + + case ViewPatientCommand.COMMAND_WORD: + return new ViewPatientCommandParser().parse(arguments); + + case ViewDoctorCommand.COMMAND_WORD: + return new ViewDoctorCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..5bced1993f83 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,6 +2,9 @@ import static java.util.Objects.requireNonNull; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -9,10 +12,14 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.appointment.ConsumptionPerDay; +import seedu.address.model.appointment.Dosage; +import seedu.address.model.appointment.MedicineName; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -21,10 +28,12 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_ID = "Appointment does not exist."; /** * 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,7 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +131,97 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String remark} into a {@code Remark}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Remark parseRemark(String remark) throws ParseException { + requireNonNull(remark); + String trimmedRemark = remark.trim(); + return new Remark(trimmedRemark); + } + + /** + * Parses a {@code String dateTime} into a {@code LocalDateTime}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code dateTime} format is invalid. + */ + public static LocalDateTime parseDateTime(String dateTime) throws ParseException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + String format = "yyyy-MM-dd HH:mm"; + LocalDateTime localDateTime = null; + try { + localDateTime = LocalDateTime.parse(trimmedDateTime, DateTimeFormatter.ofPattern(format)); + } catch (DateTimeParseException e) { + throw new ParseException("DateTime should be in this format: " + format); + } + return localDateTime; + } + + /** + * Parses a {@code String id} into a {@code int}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code id} format is invalid. + */ + public static int parseId(String id) throws ParseException { + requireNonNull(id); + String trimmedId = id.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedId)) { + throw new ParseException(MESSAGE_INVALID_ID); + } + return Integer.parseInt(trimmedId); + } + + /** + * Parses a {@code String medicineName} into a {@code MedicineName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code medicineName} format is invalid. + */ + public static MedicineName parseMedicineName(String medicineName) throws ParseException { + requireNonNull(medicineName); + String trimmedMedicineName = medicineName.trim(); + if (!MedicineName.isValidMedicineName(trimmedMedicineName)) { + throw new ParseException(MedicineName.MESSAGE_MEDICINE_NAME_CONSTRAINTS); + } + return new MedicineName(trimmedMedicineName); + } + + /** + * Parses a {@code String dosage} into a {@code Dosage}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code dosage} format is invalid. + */ + public static Dosage parseDosage(String dosage) throws ParseException { + requireNonNull(dosage); + String trimmedDosage = dosage.trim(); + if (!Dosage.isValidDosage(trimmedDosage)) { + throw new ParseException(Dosage.MESSAGE_CONSTRAINTS); + } + return new Dosage(trimmedDosage); + } + + /** + * Parses a {@code String consumptionPerDay} into a {@code ConsumptionPerDay}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code consumptionPerDay} format is invalid. + */ + public static ConsumptionPerDay parseConsumptionPerDay(String consumptionPerDay) throws ParseException { + requireNonNull(consumptionPerDay); + String trimmedConsumptionPerDay = consumptionPerDay.trim(); + if (!ConsumptionPerDay.isValidConsumptionPerDay(trimmedConsumptionPerDay)) { + throw new ParseException(ConsumptionPerDay.MESSAGE_CONSTRAINTS); + } + return new ConsumptionPerDay(trimmedConsumptionPerDay); + } + + + + } diff --git a/src/main/java/seedu/address/logic/parser/RegisterDoctorCommandParser.java b/src/main/java/seedu/address/logic/parser/RegisterDoctorCommandParser.java new file mode 100644 index 000000000000..4c2d742a6f1a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RegisterDoctorCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.RegisterDoctorCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddDoctorCommand object + */ +public class RegisterDoctorCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddDoctorCommand + * and returns an AddDoctorCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RegisterDoctorCommand 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, + RegisterDoctorCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Remark remark = new Remark(""); //register doctor currently doesn't support register remark + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + tagList.add(new Tag("Doctor")); + + Doctor doctor = new Doctor(name, phone, email, address, remark, tagList); + + return new RegisterDoctorCommand(doctor); + } + + /** + * 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/RegisterPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/RegisterPatientCommandParser.java new file mode 100644 index 000000000000..3577df9cb8a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RegisterPatientCommandParser.java @@ -0,0 +1,65 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.RegisterPatientCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddPatientCommand object + */ +public class RegisterPatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPatientCommand + * and returns an AddPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RegisterPatientCommand 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, + RegisterPatientCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Remark remark = new Remark(""); //register patient currently doesn't support register remark + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + tagList.add(new Tag("Patient")); + String telegramId = "123123"; + + Patient person = new Patient(name, phone, email, address, remark, tagList, telegramId); + + return new RegisterPatientCommand(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/RemarkCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java new file mode 100644 index 000000000000..3a713b26746e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.RemarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; + + +/** + * Parses input arguments and creates a new RemarkCommand object + */ +public class RemarkCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the RemarkCommand + * and returns an RemarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + + public RemarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_REMARK); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_REMARK) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemarkCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Remark remark = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get()); + Phone phone = null; + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + return new RemarkCommand(name, phone, remark); + + } + + /** + * 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/ViewDoctorCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewDoctorCommandParser.java new file mode 100644 index 000000000000..149b68944802 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewDoctorCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.ViewDoctorCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new ViewDoctorCommand object + */ +public class ViewDoctorCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewDoctorCommand + * and returns an ViewDoctorCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewDoctorCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE); + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewDoctorCommand.MESSAGE_USAGE)); + } + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = null; + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + return new ViewDoctorCommand(name, phone); + } + + /** + * 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/ViewPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewPatientCommandParser.java new file mode 100644 index 000000000000..2d7849cfdb6c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewPatientCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.ViewPatientCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new ViewPatientCommand object + */ +public class ViewPatientCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewPatientCommand + * and returns an ViewPatientCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewPatientCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE); + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewPatientCommand.MESSAGE_USAGE)); + } + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = null; + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + return new ViewPatientCommand(name, phone); + } + + /** + * 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/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 7f85c8b9258b..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void updatePerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/HealthBook.java b/src/main/java/seedu/address/model/HealthBook.java new file mode 100644 index 000000000000..7c044166c415 --- /dev/null +++ b/src/main/java/seedu/address/model/HealthBook.java @@ -0,0 +1,257 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; + +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; +import seedu.address.model.tag.Tag; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class HealthBook implements ReadOnlyHealthBook { + + private final UniquePersonList persons; + private final UniqueAppointmentList appointments; + private int appointmentCounter; + + /* + * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + persons = new UniquePersonList(); + appointments = new UniqueAppointmentList(); + appointmentCounter = 10000; + } + + public HealthBook() { + } + + /** + * Creates an HealthBook using the Persons in the {@code toBeCopied} + */ + public HealthBook(ReadOnlyHealthBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the person list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setPersons(List persons) { + this.persons.setPersons(persons); + } + + /** + * Replaces the contents of the person list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setAppointments(List appointments) { + this.appointments.setAppointments(appointments); + } + + /** + * Replaces the contents of the appointment counter with {@code appointmentCounter}. + */ + public void setAppointmentCounter(int appointmentCounter) { + this.appointmentCounter = appointmentCounter; + } + + /** + * Resets the existing data of this {@code HealthBook} with {@code newData}. + */ + public void resetData(ReadOnlyHealthBook newData) { + requireNonNull(newData); + + setPersons(newData.getPersonList()); + setAppointments(newData.getAppointmentList()); + setAppointmentCounter(newData.getAppointmentCounter()); + } + + //// person-level operations + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + public boolean hasPerson(Person person) { + requireNonNull(person); + return persons.contains(person); + } + + /** + * Adds a person to the address book. + * The person must not already exist in the address book. + */ + public void addPerson(Person p) { + persons.add(p); + } + + /** + * Adds a patient to the address book. + * The person must not already exist in the address book. + */ + public void addPatient(Patient p) { + persons.add(p); + } + + /** + * Adds a doctor to the address book. + * The person must not already exist in the address book. + */ + public void addDoctor(Doctor d) { + persons.add(d); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the address book. + * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + */ + public void updatePerson(Person target, Person editedPerson) { + requireNonNull(editedPerson); + + persons.setPerson(target, editedPerson); + } + + /** + * Replaces the given appointment {@code target} in the list with {@code editedAppointment}. + * {@code target} must exist in the address book. + * The appointment identity of {@code editedAppointment} must not be the same as another existing appointment + * in the address book. + */ + public void setAppointment(Appointment target, Appointment editedAppointment) { + requireNonNull(editedAppointment); + + appointments.setAppointment(target, editedAppointment); + } + + /** + * Removes {@code key} from this {@code HealthBook}. + * {@code key} must exist in the address book. + */ + public void removePerson(Person key) { + if (key.getTags().contains(new Tag("Patient"))) { + for (Appointment patientApp : ((Patient) key).getUpcomingAppointments()) { + outerloop: + for (Person person : persons) { + if (person.getTags().contains(new Tag("Doctor"))) { + Doctor doctor = ((Doctor) person); + for (Appointment doctorApp : doctor.getUpcomingAppointments()) { + if (patientApp.getAppointmentId() == doctorApp.getAppointmentId()) { + doctor.deleteAppointment(patientApp); + appointments.remove(patientApp); + break outerloop; + } + } + } + } + } + } else if (key.getTags().contains(new Tag("Doctor"))) { + for (Appointment doctorApp : ((Doctor) key).getUpcomingAppointments()) { + outerloop: + for (Person person : persons) { + if (person.getTags().contains(new Tag("Patient"))) { + Patient patient = ((Patient) person); + for (Appointment patientApp : patient.getUpcomingAppointments()) { + if (patientApp.getAppointmentId() == doctorApp.getAppointmentId()) { + patient.deleteAppointment(doctorApp); + appointments.remove(doctorApp); + break outerloop; + } + } + } + } + } + } + persons.remove(key); + } + + /** + * Adds appointment to patient {@code patient, appointment} to this {@code HealthBook}. + * {@code patient} must exist in the health book. + */ + public void addAppointment(Appointment appointment) { + appointments.add(appointment); + } + + /** + * Deletes a patient's {@code appointment} from this {@code HealthBook}. + */ + public void deleteAppointment(Appointment appointment, Patient patient, Doctor doctor) { + Patient targetPatient = persons.findPatient(patient); + Doctor targetDoctor = persons.findDoctor(doctor); + patient.deleteAppointment(appointment); + doctor.deleteAppointment(appointment); + persons.setPerson(targetPatient, patient); + persons.setPerson(targetDoctor, doctor); + appointments.remove(appointment); + } + + /** + * Completes a patient's {@code appointment} from this {@code HealthBook}. + */ + public void completeAppointment(Appointment appointment, Patient patient, Doctor doctor) { + Patient targetPatient = persons.findPatient(patient); + Doctor targetDoctor = persons.findDoctor(doctor); + patient.completeUpcomingAppointment(appointment); + doctor.completeUpcomingAppointment(appointment); + persons.setPerson(targetPatient, patient); + persons.setPerson(targetDoctor, doctor); + appointments.setToComplete(appointment); + } + + //// util methods + + @Override + public String toString() { + return persons.asUnmodifiableObservableList().size() + " persons"; + // TODO: refine later + } + + @Override + public ObservableList getPersonList() { + return persons.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getAppointmentList() { + return appointments.asUnmodifiableObservableList(); + } + + @Override + public int getAppointmentCounter() { + return appointmentCounter; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HealthBook // instanceof handles nulls + && persons.equals(((HealthBook) other).persons)) + && (other instanceof HealthBook + && appointments.equals(((HealthBook) other).appointments)) + && (other instanceof HealthBook // instanceof handles nulls + && appointmentCounter == (((HealthBook) other).appointmentCounter)); + } + + @Override + public int hashCode() { + return persons.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..b1bfc9458849 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,20 +3,32 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; + +import seedu.address.model.appointment.Appointment; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; import seedu.address.model.person.Person; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ + /** + * {@code Predicate} that always evaluate to true + */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + Predicate PREDICATE_SHOW_ALL_APPOINTMENTS = unused -> true; - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetData(ReadOnlyHealthBook newData); + + /** + * Returns the HealthBook + */ + ReadOnlyHealthBook getAddressBook(); /** * Returns true if a person with the same identity as {@code person} exists in the address book. @@ -25,52 +37,112 @@ public interface Model { /** * Deletes the given person. - * The person must exist in the address book. + * The person must exist in the health book. */ void deletePerson(Person target); /** * Adds the given person. - * {@code person} must not already exist in the address book. + * {@code person} must not already exist in the health book. */ void addPerson(Person person); + /** + * Adds the given patient. + * {@code patient} must not already exist in the health book. + */ + void addPatient(Patient patient); + + /** + * Adds the given doctor. + * {@code doctor} must not already exist in the health book. + */ + void addDoctor(Doctor doctor); + /** * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * {@code target} must exist in the health book. + * The person identity of {@code editedPerson} must not be the same as another existing person in the health book. */ void updatePerson(Person target, Person editedPerson); - /** Returns an unmodifiable view of the filtered person list */ + /** + * Replaces the given appointment {@code target} with {@code editedAppointment} + * {@code target} must exist in the health book. + * The appointment identity of {@code editedAppointment} must not be the same as another existing appointment + * in the health book. + */ + void setAppointment(Appointment target, Appointment editedAppointment); + + /** + * Returns an unmodifiable view of the filtered person list + */ ObservableList getFilteredPersonList(); /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); /** - * Returns true if the model has previous address book states to restore. + * Returns an unmodifiable view of the filtered appointment list + */ + ObservableList getFilteredAppointmentList(); + + /** + * Updates the filter of the filtered appointment list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredAppointmentList(Predicate predicate); + + /** + * Returns current appointmentCounter + */ + int getAppointmentCounter(); + + /** + * Increase the Appointment Counter + */ + void incrementAppointmentCounter(); + + /** + * Returns true if the model has previous health book states to restore. */ boolean canUndoAddressBook(); /** - * Returns true if the model has undone address book states to restore. + * Returns true if the model has undone health book states to restore. */ boolean canRedoAddressBook(); /** - * Restores the model's address book to its previous state. + * Restores the model's health book to its previous state. */ void undoAddressBook(); /** - * Restores the model's address book to its previously undone state. + * Restores the model's health book to its previously undone state. */ void redoAddressBook(); + /** + * Adds appointment. + */ + void addAppointment(Appointment appointment); + + /** + * Deletes appointment. + */ + void deleteAppointment(Appointment appointment, Patient patient, Doctor doctor); + + /** + * Completes appointment. + */ + void completeAppointment(Appointment appointment, Patient patient, Doctor doctor); + /** * Saves the current address book state for undo/redo. */ diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..9e3db06a2321 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -8,10 +8,15 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; + import javafx.collections.transformation.FilteredList; + import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.HealthBookChangedEvent; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; import seedu.address.model.person.Person; /** @@ -20,40 +25,45 @@ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final VersionedAddressBook versionedAddressBook; + private final VersionedHealthBook versionedAddressBook; private final FilteredList filteredPersons; + private final FilteredList filteredAppointment; + private int appointmentCounter; /** * Initializes a ModelManager with the given addressBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyHealthBook addressBook, UserPrefs userPrefs) { super(); requireAllNonNull(addressBook, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - versionedAddressBook = new VersionedAddressBook(addressBook); + versionedAddressBook = new VersionedHealthBook(addressBook); filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + filteredAppointment = new FilteredList<>(versionedAddressBook.getAppointmentList()); + appointmentCounter = versionedAddressBook.getAppointmentCounter(); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new HealthBook(), new UserPrefs()); } @Override - public void resetData(ReadOnlyAddressBook newData) { + public void resetData(ReadOnlyHealthBook newData) { versionedAddressBook.resetData(newData); indicateAddressBookChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { + public ReadOnlyHealthBook getAddressBook() { return versionedAddressBook; } - /** Raises an event to indicate the model has changed */ + /** + * Raises an event to indicate the model has changed + */ private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); + raise(new HealthBookChangedEvent(versionedAddressBook)); } @Override @@ -75,6 +85,20 @@ public void addPerson(Person person) { indicateAddressBookChanged(); } + @Override + public void addPatient(Patient patient) { + versionedAddressBook.addPatient(patient); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + + @Override + public void addDoctor(Doctor doctor) { + versionedAddressBook.addDoctor(doctor); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + @Override public void updatePerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); @@ -83,6 +107,40 @@ public void updatePerson(Person target, Person editedPerson) { indicateAddressBookChanged(); } + @Override + public void setAppointment(Appointment target, Appointment editedAppointment) { + requireAllNonNull(target, editedAppointment); + + versionedAddressBook.setAppointment(target, editedAppointment); + indicateAddressBookChanged(); + } + + @Override + public void addAppointment(Appointment appointment) { + requireAllNonNull(appointment); + versionedAddressBook.addAppointment(appointment); + indicateAddressBookChanged(); + } + + @Override + public void deleteAppointment(Appointment appointment, Patient patient, Doctor doctor) { + requireAllNonNull(appointment); + requireNonNull(patient); + requireNonNull(doctor); + versionedAddressBook.deleteAppointment(appointment, patient, doctor); + indicateAddressBookChanged(); + } + + @Override + public void completeAppointment(Appointment appointment, Patient patient, Doctor doctor) { + requireNonNull(appointment); + requireNonNull(patient); + requireNonNull(doctor); + versionedAddressBook.completeAppointment(appointment, patient, doctor); + indicateAddressBookChanged(); + } + + //=========== Filtered Person List Accessors ============================================================= /** @@ -100,6 +158,41 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Filtered Appointment List Accessors ======================================================== + + /** + * Returns an unmodifiable view of the list of {@code Appointment} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredAppointmentList() { + return FXCollections.unmodifiableObservableList(filteredAppointment); + } + + @Override + public void updateFilteredAppointmentList(Predicate predicate) { + requireNonNull(predicate); + filteredAppointment.setPredicate(predicate); + } + + //=========== Appointment Counter Accessors ======================================================== + + /** + * Returns an {@code Appointment Counter} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public int getAppointmentCounter() { + return appointmentCounter; + } + + @Override + public void incrementAppointmentCounter() { + appointmentCounter++; + versionedAddressBook.setAppointmentCounter(appointmentCounter); + indicateAddressBookChanged(); + } + //=========== Undo/Redo ================================================================================= @Override diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a290..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyHealthBook.java b/src/main/java/seedu/address/model/ReadOnlyHealthBook.java new file mode 100644 index 000000000000..594659c541bd --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyHealthBook.java @@ -0,0 +1,29 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.person.Person; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyHealthBook { + + /** + * Returns an unmodifiable view of the persons list. + * This list will not contain any duplicate persons. + */ + ObservableList getPersonList(); + + /** + * Returns an unmodifiable view of the appointment list. + * This list will not contain any duplicate appointment. + */ + ObservableList getAppointmentList(); + + /** + * Returns an current appointment. + */ + int getAppointmentCounter(); + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..38151cfb73fa 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,7 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path healthBookFilePath = Paths.get("data" , "healthbook.xml"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -30,12 +30,12 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getHealthBookFilePath() { + return healthBookFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setAddressBookFilePath(Path healthBookFilePath) { + this.healthBookFilePath = healthBookFilePath; } @Override @@ -50,19 +50,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath); + && Objects.equals(healthBookFilePath, o.healthBookFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, healthBookFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + healthBookFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedHealthBook.java similarity index 81% rename from src/main/java/seedu/address/model/VersionedAddressBook.java rename to src/main/java/seedu/address/model/VersionedHealthBook.java index 227a335045d7..e7ee80b5f5c7 100644 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ b/src/main/java/seedu/address/model/VersionedHealthBook.java @@ -4,28 +4,28 @@ import java.util.List; /** - * {@code AddressBook} that keeps track of its own history. + * {@code HealthBook} that keeps track of its own history. */ -public class VersionedAddressBook extends AddressBook { +public class VersionedHealthBook extends HealthBook { - private final List addressBookStateList; + private final List addressBookStateList; private int currentStatePointer; - public VersionedAddressBook(ReadOnlyAddressBook initialState) { + public VersionedHealthBook(ReadOnlyHealthBook initialState) { super(initialState); addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); + addressBookStateList.add(new HealthBook(initialState)); currentStatePointer = 0; } /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. + * Saves a copy of the current {@code HealthBook} state at the end of the state list. * Undone states are removed from the state list. */ public void commit() { removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); + addressBookStateList.add(new HealthBook(this)); currentStatePointer++; } @@ -77,11 +77,11 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { + if (!(other instanceof VersionedHealthBook)) { return false; } - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; + VersionedHealthBook otherVersionedAddressBook = (VersionedHealthBook) other; // state check return super.equals(otherVersionedAddressBook) diff --git a/src/main/java/seedu/address/model/appointment/Appointment.java b/src/main/java/seedu/address/model/appointment/Appointment.java new file mode 100644 index 000000000000..d3ec78d49308 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/Appointment.java @@ -0,0 +1,170 @@ +package seedu.address.model.appointment; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents an Appointment in the health book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Appointment implements Comparable { + + private AppointmentId appointmentId; + private String doctor; + private String patient; + private LocalDateTime dateTime; + private Status status; + private String comments; + private List prescriptions; + + public Appointment(int appointmentCounter, String doctor, String patient, LocalDateTime dateTime) { + appointmentId = new AppointmentId(appointmentCounter); + this.doctor = doctor; + this.patient = patient; + this.dateTime = dateTime; + this.status = Status.UPCOMING; + prescriptions = new ArrayList<>(); + } + + public Appointment(AppointmentId appointmentId, String doctor, String patient, + LocalDateTime dateTime, Status status, + String comments, List prescriptions) { + this.appointmentId = appointmentId; + this.doctor = doctor; + this.patient = patient; + this.dateTime = dateTime; + this.status = status; + if (comments != null) { + this.comments = comments; + } + if (prescriptions != null) { + this.prescriptions = prescriptions; + } else { + this.prescriptions = new ArrayList<>(); + } + } + + public int getAppointmentId() { + return appointmentId.getAppointmentId(); + } + + public String getDoctor() { + return doctor; + } + + public String getPatient() { + return patient; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public Status getStatus() { + return status; + } + + public String getComments() { + return comments; + } + + public List getPrescriptions() { + return prescriptions; + } + + public void addPrescription(Prescription prescription) { + prescriptions.add(prescription); + } + + /** + * already verified that prescription exists + */ + public void deletePrescription(String medicineName) { + Prescription toRemove = null; + for (Prescription p : prescriptions) { + if (p.getMedicineName().equals(new MedicineName(medicineName))) { + toRemove = p; + } + } + prescriptions.remove(toRemove); + } + + public void completeAppointment() { + status = Status.COMPLETED; + } + + /** + * Checks if target appointment is later than appointment argument by LocalDateTime. + * Return true if target appointment is later than other appointment. Else, return false. + */ + public boolean isLaterThan(Appointment appointmentToCheck) { + return dateTime.isAfter(appointmentToCheck.dateTime); + } + + /** + * Returns true if {@code appointments} has the same appointmentId. + */ + public boolean isSameAppointment(Appointment toCheck) { + return this.appointmentId.getAppointmentId() == toCheck.getAppointmentId(); + } + + /** + * Returns true if {@code appointments} has the clash with appointmentId. + */ + public boolean hasClashAppointment(Appointment toCheck) { + LocalDateTime lowerBound = this.dateTime; + LocalDateTime upperBound = this.dateTime.plusMinutes(30); + LocalDateTime timeToCheckStart = toCheck.getDateTime(); + LocalDateTime timeToCheckEnd = toCheck.getDateTime().plusMinutes(30); + + // Check if start of appointment toCheck is between current appointment. + if (timeToCheckStart.equals(lowerBound) + || (timeToCheckStart.isAfter(lowerBound) && timeToCheckStart.isBefore(upperBound))) { + return true; + } + + // Check if end of appointment toCheck is between current appointment. + if (timeToCheckEnd.equals(upperBound) + || (timeToCheckEnd.isAfter(lowerBound) && timeToCheckEnd.isBefore(upperBound))) { + return true; + } + return false; + } + + /** + * Returns 1 if {@code appointment} is earlier than other appointment + */ + @Override + public int compareTo(Appointment o) { + if (this.isLaterThan(o)) { + return -1; + } else if (!this.isLaterThan(o)) { + return 1; + } else { + return 0; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Appointment)) { + return false; + } + + /** + * Returns true if both appointments have the same identity and data fields. + * This defines a stronger notion of equality between two appointments. + */ + + Appointment otherAppointment = (Appointment) other; + return otherAppointment.getAppointmentId() == getAppointmentId() + && otherAppointment.getDoctor().equals(getDoctor()) + && otherAppointment.getPatient().equals(getPatient()) + && otherAppointment.getDateTime().equals(getDateTime()); + } +} diff --git a/src/main/java/seedu/address/model/appointment/AppointmentId.java b/src/main/java/seedu/address/model/appointment/AppointmentId.java new file mode 100644 index 000000000000..0e796bfef214 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/AppointmentId.java @@ -0,0 +1,27 @@ +package seedu.address.model.appointment; + +/** + * Represents an status of an appointment in the health book. + */ +public class AppointmentId { + private int appointmentId; + + public AppointmentId(int id) { + appointmentId = id; + } + + public int getAppointmentId() { + return appointmentId; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AppointmentId)) { + return false; + } + return appointmentId == ((AppointmentId) other).appointmentId; + } +} diff --git a/src/main/java/seedu/address/model/appointment/ConsumptionPerDay.java b/src/main/java/seedu/address/model/appointment/ConsumptionPerDay.java new file mode 100644 index 000000000000..1c0555651bfd --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/ConsumptionPerDay.java @@ -0,0 +1,54 @@ +package seedu.address.model.appointment; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents number of consumption per day in prescription + */ +public class ConsumptionPerDay { + public static final String MESSAGE_CONSTRAINTS = + "ConsumptionPerDay should only contain numbers, and it must be greater than 0"; + public static final String CONSUMPTION_PER_DAY_VALIDATION_REGEX = "^[1-9][0-9]*$"; + private final String value; + + /** + * Constructs a {@code consumptionPerDay}. + * + * @param consumptionPerDay A valid consumptionPerDay + */ + public ConsumptionPerDay(String consumptionPerDay) { + requireNonNull(consumptionPerDay); + checkArgument(isValidConsumptionPerDay(consumptionPerDay), MESSAGE_CONSTRAINTS); + value = consumptionPerDay; + } + + public String getValue() { + return value; + } + + /** + * Returns true if a given string is a valid number of consumption per day + */ + public static boolean isValidConsumptionPerDay(String test) { + return test.matches(CONSUMPTION_PER_DAY_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ConsumptionPerDay // instanceof handles nulls + && value.equals(((ConsumptionPerDay) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/appointment/Dosage.java b/src/main/java/seedu/address/model/appointment/Dosage.java new file mode 100644 index 000000000000..adf88fb794c4 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/Dosage.java @@ -0,0 +1,60 @@ +package seedu.address.model.appointment; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents dosage in prescription + */ + +public class Dosage { + + public static final String MESSAGE_CONSTRAINTS = + "Dosage should only contain numbers, and it must be greater than 0"; + public static final String DOSAGE_VALIDATION_REGEX = "^[1-9][0-9]*$"; + private final String value; + + /** + * Constructs a {@code Dosage}. + * + * @param dosage A valid dosage + */ + public Dosage(String dosage) { + requireNonNull(dosage); + checkArgument(isValidDosage(dosage), MESSAGE_CONSTRAINTS); + value = dosage; + } + + public Dosage() { + value = ""; + } + + public String getValue() { + return value; + } + + /** + * Returns true if a given string is a valid dosage + */ + public static boolean isValidDosage(String test) { + return test.matches(DOSAGE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Dosage // instanceof handles nulls + && value.equals(((Dosage) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/appointment/MedicineName.java b/src/main/java/seedu/address/model/appointment/MedicineName.java new file mode 100644 index 000000000000..7efe1fc29f6a --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/MedicineName.java @@ -0,0 +1,65 @@ +package seedu.address.model.appointment; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Medicine Name in prescription + */ +public class MedicineName { + + public static final String MESSAGE_MEDICINE_NAME_CONSTRAINTS = + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /** + * the first character of the medicine name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String MEDICINE_NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + private final String fullMedicineName; + + /** + * Constructs a {@code MedicineName}. + * + * @param medicineName A valid name. + */ + public MedicineName(String medicineName) { + requireNonNull(medicineName); + checkArgument(isValidMedicineName(medicineName), MESSAGE_MEDICINE_NAME_CONSTRAINTS); + fullMedicineName = medicineName; + } + + public MedicineName() { + fullMedicineName = ""; + } + + public String getFullMedicineName() { + return fullMedicineName; + } + + /** + * Returns true if a given string is a valid MedicineName. + */ + public static boolean isValidMedicineName(String test) { + return test.matches(MEDICINE_NAME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullMedicineName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MedicineName // instanceof handles nulls + && fullMedicineName.toLowerCase().equals(((MedicineName) other).fullMedicineName.toLowerCase())); + } + + @Override + public int hashCode() { + return fullMedicineName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/appointment/Prescription.java b/src/main/java/seedu/address/model/appointment/Prescription.java new file mode 100644 index 000000000000..a5952f2d78c8 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/Prescription.java @@ -0,0 +1,79 @@ +package seedu.address.model.appointment; + +import java.util.Objects; + +/** + * Represents an Prescription in the health book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Prescription { + private int id; + private MedicineName medicineName; + private Dosage dosage; + private ConsumptionPerDay consumptionPerDay; + + public Prescription(int appointmentId, MedicineName medicineName, Dosage dosage, + ConsumptionPerDay consumptionPerDay) { + this.id = appointmentId; + this.medicineName = medicineName; + this.dosage = dosage; + this.consumptionPerDay = consumptionPerDay; + } + + // Get Methods + public int getId() { + return id; + } + + public MedicineName getMedicineName() { + return medicineName; + } + + public Dosage getDosage() { + return dosage; + } + + public ConsumptionPerDay getConsumptionPerDay() { + return consumptionPerDay; + } + + /** + * Returns true if both prescriptions have the same identity and data fields. + * This defines a stronger notion of equality between two prescriptions. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Prescription)) { + return false; + } + + /** + * Equality of prescription only determined by name + */ + Prescription otherPrescription = (Prescription) other; + return otherPrescription.getMedicineName().toString().toLowerCase() + .equals(getMedicineName().toString().toLowerCase()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(id, medicineName, dosage, consumptionPerDay); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Medicine Name: ") + .append(getMedicineName()) + .append(" Dosage: ") + .append(getDosage()) + .append(" Consumption Per Day: ") + .append(getConsumptionPerDay()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/appointment/Status.java b/src/main/java/seedu/address/model/appointment/Status.java new file mode 100644 index 000000000000..3f91913a46a4 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/Status.java @@ -0,0 +1,8 @@ +package seedu.address.model.appointment; + +/** + * Represents an status of an appointment in the health book. + */ +public enum Status { + UPCOMING, COMPLETED +} diff --git a/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java b/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java new file mode 100644 index 000000000000..26757fbc2028 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java @@ -0,0 +1,158 @@ +package seedu.address.model.appointment; + +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.appointment.exceptions.AppointmentNotFoundException; +import seedu.address.model.appointment.exceptions.DuplicateAppointmentException; + +/** + * A list of appointments that enforces uniqueness between its elements and does not allow nulls. + * A appointment is considered unique by comparing using {@code Appointment#isSameAppointment(Appointment)}. + * As such, adding and updating of appointments uses Appointment#isSameAppointment(Appointment) for equality so + * as to ensure that the appointment being added or updated is unique in terms of identity in the UniqueAppointmentList. + * However, the removal of a appointment uses Appointment#equals(Object) so as to ensure that the appointment with + * exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Appointment#isSameAppointment(Appointment) + */ +public class UniqueAppointmentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent appointment as the given argument. + */ + public boolean contains(Appointment toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameAppointment); + } + + /** + * Adds a appointment to the list. + * The appointment must not already exist in the list. + */ + public void add(Appointment toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateAppointmentException(); + } + internalList.add(toAdd); + } + + /** + * Completes a appointment in the list. + * The appointment must not already exist in the list. + */ + public void setToComplete(Appointment toComplete) { + requireNonNull(toComplete); + int index = internalList.indexOf(toComplete); + if (index == -1) { + throw new AppointmentNotFoundException(); + } + toComplete.completeAppointment(); + internalList.set(index, toComplete); + } + + /** + * Replaces the appointment {@code target} in the list with {@code editedAppointment}. + * {@code target} must exist in the list. + * The appointment identity of {@code editedAppointment} must not be the same as another existing appointment + * in the list. + */ + public void setAppointment(Appointment target, Appointment editedAppointment) { + requireAllNonNull(target, editedAppointment); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new AppointmentNotFoundException(); + } + + if (!target.isSameAppointment(editedAppointment) && contains(editedAppointment)) { + throw new DuplicateAppointmentException(); + } + internalList.set(index, editedAppointment); + } + + /** + * Removes the equivalent appointment from the list. + * The appointment must exist in the list. + */ + public void remove(Appointment toRemove) { + requireNonNull(toRemove); + Appointment appointmentToBeDeletedInList = null; + for (Appointment appt : internalList) { + if (appt.getAppointmentId() == toRemove.getAppointmentId()) { + appointmentToBeDeletedInList = appt; + } + } + if (appointmentToBeDeletedInList == null) { + throw new AppointmentNotFoundException(); + } + + internalList.remove(appointmentToBeDeletedInList); + } + + public void setAppointments(UniqueAppointmentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code appointments}. + * {@code appointments} must not contain duplicate appointments. + */ + public void setAppointments(List appointments) { + requireAllNonNull(appointments); + if (!appointmentsAreUnique(appointments)) { + throw new DuplicateAppointmentException(); + } + + internalList.setAll(appointments); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueAppointmentList // instanceof handles nulls + && internalList.equals(((UniqueAppointmentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code appointments} contains only unique appointments. + */ + private boolean appointmentsAreUnique(List appointments) { + for (int i = 0; i < appointments.size() - 1; i++) { + for (int j = i + 1; j < appointments.size(); j++) { + if (appointments.get(i).isSameAppointment(appointments.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java new file mode 100644 index 000000000000..5dc5c48d3d82 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that the operation is unable to find the specified appointment. + */ +public class AppointmentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java b/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java new file mode 100644 index 000000000000..4239b9f0ffb8 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java @@ -0,0 +1,11 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateAppointmentException extends RuntimeException { + public DuplicateAppointmentException() { + super("Operation would result in duplicate appointments"); + } +} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/InvalidInputOutputException.java b/src/main/java/seedu/address/model/appointment/exceptions/InvalidInputOutputException.java new file mode 100644 index 000000000000..fa2dc20f37f4 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/InvalidInputOutputException.java @@ -0,0 +1,10 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that user that the input is incorrect + */ +public class InvalidInputOutputException extends RuntimeException { + public InvalidInputOutputException() { + super("The input you sent to google calendar is invalid"); + } +} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/InvalidSecurityAccessException.java b/src/main/java/seedu/address/model/appointment/exceptions/InvalidSecurityAccessException.java new file mode 100644 index 000000000000..e9ecc7c52116 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/InvalidSecurityAccessException.java @@ -0,0 +1,10 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that user do not have security access for the operation + */ +public class InvalidSecurityAccessException extends RuntimeException { + public InvalidSecurityAccessException() { + super("You do not have a valid access to the google calendar"); + } +} diff --git a/src/main/java/seedu/address/model/doctor/Doctor.java b/src/main/java/seedu/address/model/doctor/Doctor.java new file mode 100644 index 000000000000..c404e6e75d1a --- /dev/null +++ b/src/main/java/seedu/address/model/doctor/Doctor.java @@ -0,0 +1,122 @@ +package seedu.address.model.doctor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import seedu.address.model.appointment.Appointment; +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.person.Remark; +import seedu.address.model.tag.Tag; + +/** + * Represents a Doctor in the health book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Doctor extends Person { + // Variables + private List upcomingAppointments; + + // Constructor + public Doctor(Name name, Phone phone, Email email, Address address, Remark remark, + Set tags) { + super(name, phone, email, address, remark, tags); + upcomingAppointments = new ArrayList<>(); + } + + public Doctor(Name name, Phone phone, Email email, Address address, Remark remark, + Set tags, List upcomingAppointments) { + super(name, phone, email, address, remark, tags); + this.upcomingAppointments = upcomingAppointments; + } + + public List getUpcomingAppointments() { + return upcomingAppointments; + } + + /** + * Adds an upcoming appointment to the doctor's queue of upcoming appointment. + * The appointment is also added into Doctor's google calendar + */ + public void addUpcomingAppointment(Appointment appointment) { + upcomingAppointments.add(appointment); + } + + /** + * Deletes appointment from doctor's queue of upcoming appointment. + */ + public void deleteAppointment(Appointment appointment) { + Appointment apptToBeDeleted = null; + for (Appointment appt : upcomingAppointments) { + if (appt.getAppointmentId() == appointment.getAppointmentId()) { + apptToBeDeleted = appt; + break; + } + } + upcomingAppointments.remove(apptToBeDeleted); + } + + /** + * Updates appointment from doctor's queue of appointment. + */ + public void setAppointment(Appointment target, Appointment editedAppointment) { + int indexToBeEdited = -1; + boolean inUpComingAppointments = false; + + for (Appointment appt : upcomingAppointments) { + if (appt.getAppointmentId() == target.getAppointmentId()) { + indexToBeEdited = upcomingAppointments.indexOf(appt); + inUpComingAppointments = true; + break; + } + } + + if (inUpComingAppointments) { + upcomingAppointments.set(indexToBeEdited, editedAppointment); + } + } + + + /** + * Completes the latest appointment of the doctor, placing the records of the appointment in to the stack of + * appointments + */ + public void completeUpcomingAppointment(Appointment targetAppointment) { + Appointment appointmentToRemove = null; + for (Appointment app : upcomingAppointments) { + if (app.isSameAppointment(targetAppointment)) { + appointmentToRemove = app; + } + } + appointmentToRemove.completeAppointment(); + upcomingAppointments.remove(appointmentToRemove); + } + + /** + * Check if the doctor contains a certain appointment by {@code appointmentId} + */ + public boolean hasAppointment(int appointmentId) { + for (Appointment app : upcomingAppointments) { + if (app.getAppointmentId() == appointmentId) { + return true; + } + } + return false; + } + + /** + * Check if appointment clash with any upcoming appointments by {@code targetAppointment} + */ + public boolean hasClashForAppointment(Appointment appointmentToCheck) { + for (Appointment app : upcomingAppointments) { + if (app.hasClashAppointment(appointmentToCheck)) { + return true; // There is a clash in the appointments + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/patient/Allergy.java b/src/main/java/seedu/address/model/patient/Allergy.java new file mode 100644 index 000000000000..f0573fb442b5 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Allergy.java @@ -0,0 +1,63 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Allergy in Patient's Medical History + */ + +public class Allergy { + + public static final String MESSAGE_ALLERGY_CONSTRAINTS = + "Allergy should only contain alphanumeric characters and spaces, and it should not be blank"; + + /** + * the first character of the allergy must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ALLERGY_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String allergy; + + /** + * Constructs a {@code Allergy}. + * + * @param allergy A valid allergy. + */ + public Allergy(String allergy) { + requireNonNull(allergy); + checkArgument(isValidAllergy(allergy), MESSAGE_ALLERGY_CONSTRAINTS); + this.allergy = allergy; + } + + public String getAllergy() { + return allergy; + } + + /** + * Returns true if a given string is a Allergy + */ + public static boolean isValidAllergy(String test) { + return test.matches(ALLERGY_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return allergy; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Allergy // instanceof handles nulls + && allergy.toLowerCase().equals(((Allergy) other).getAllergy().toLowerCase())); + } + + @Override + public int hashCode() { + return allergy.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/patient/Condition.java b/src/main/java/seedu/address/model/patient/Condition.java new file mode 100644 index 000000000000..5fb8b055d344 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Condition.java @@ -0,0 +1,62 @@ +package seedu.address.model.patient; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents Condition of a inside MedicalHistory of a Patient + * Guarantees: + */ +public class Condition { + + public static final String MESSAGE_CONDITION_CONSTRAINTS = + "Condition should only contain alphanumeric characters and spaces, and it should not be blank"; + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String CONDITION_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String condition; + + /** + * Constructs a {@code Condition}. + * + * @param condition A valid condition. + */ + public Condition(String condition) { + requireNonNull(condition); + checkArgument(isValidCondition(condition), CONDITION_VALIDATION_REGEX); + this.condition = condition; + } + + public String getCondition() { + return condition; + } + + /** + * Returns true if a given string is a valid condition. + */ + public static boolean isValidCondition(String test) { + return test.matches(CONDITION_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return condition; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Condition // instanceof handles nulls + && condition.toLowerCase().equals(((Condition) other).condition.toLowerCase())); // state check + } + + @Override + public int hashCode() { + return condition.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/patient/MedicalHistory.java b/src/main/java/seedu/address/model/patient/MedicalHistory.java new file mode 100644 index 000000000000..1a7e5bc807b0 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/MedicalHistory.java @@ -0,0 +1,69 @@ +package seedu.address.model.patient; + +import java.util.ArrayList; + +/** + * Represents a Medical History of a patient in the health book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class MedicalHistory { + private ArrayList allergies; + private ArrayList conditions; + + public MedicalHistory() { + allergies = new ArrayList<>(); + conditions = new ArrayList<>(); + } + public MedicalHistory(ArrayList allergies, ArrayList conditions) { + this.allergies = allergies; + this.conditions = conditions; + } + public MedicalHistory(MedicalHistory medicalHistory) { + this.allergies = new ArrayList<>(medicalHistory.getAllergies()); + this.conditions = new ArrayList<>(medicalHistory.getConditions()); + } + + public ArrayList getAllergies() { + return allergies; + } + + public ArrayList getConditions() { + return conditions; + } + + public void addAllergy(Allergy allergy) { + allergies.add(allergy); + } + + public void addAllergy(String allergy) { + allergies.add(new Allergy(allergy)); + } + + public void addCondition(Condition condition) { + conditions.add(condition); + } + + public void addCondition(String condition) { + conditions.add(new Condition(condition)); + } + + public void setAllergies(ArrayList allergies) { + this.allergies = allergies; + } + + public void setConditions(ArrayList conditions) { + this.conditions = conditions; + } + + @Override + public boolean equals(Object o) { + if (o == this) { //if same object + return true; + } else if (!(o instanceof MedicalHistory)) { + return false; + } else { + MedicalHistory r = (MedicalHistory) o; + return allergies.equals(r.getAllergies()) && conditions.equals(r.getConditions()); + } + } +} diff --git a/src/main/java/seedu/address/model/patient/Patient.java b/src/main/java/seedu/address/model/patient/Patient.java new file mode 100644 index 000000000000..742f32ab3636 --- /dev/null +++ b/src/main/java/seedu/address/model/patient/Patient.java @@ -0,0 +1,198 @@ +package seedu.address.model.patient; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import seedu.address.model.appointment.Appointment; +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.person.Remark; +import seedu.address.model.tag.Tag; + +/** + * Represents a Patient in the health book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Patient extends Person { + // Variables + private String telegramId; + private MedicalHistory medicalHistory; + private List upcomingAppointments; + private List pastAppointments; + + // Constructor + + public Patient(Name name, Phone phone, Email email, Address address, Remark remark, + Set tags, String telegramId) { + super(name, phone, email, address, remark, tags); + setTelegramId(telegramId); + upcomingAppointments = new ArrayList<>(); + pastAppointments = new ArrayList<>(); + this.medicalHistory = new MedicalHistory(); + } + + public Patient(Name name, Phone phone, Email email, Address address, Remark remark, + Set tags, String telegramId, List upcomingAppointments, + List pastAppointments) { + super(name, phone, email, address, remark, tags); + setTelegramId(telegramId); + this.upcomingAppointments = upcomingAppointments; + this.pastAppointments = pastAppointments; + this.medicalHistory = new MedicalHistory(); + } + + public Patient(Name name, Phone phone, Email email, Address address, Remark remark, + Set tags, String telegramId, List upcomingAppointments, + List pastAppointments, MedicalHistory medicalHistory) { + super(name, phone, email, address, remark, tags); + setTelegramId(telegramId); + this.upcomingAppointments = upcomingAppointments; + this.pastAppointments = pastAppointments; + this.medicalHistory = medicalHistory; + + } + + public List getUpcomingAppointments() { + return upcomingAppointments; + } + + public List getPastAppointments() { + return pastAppointments; + } + + public String getTelegramId() { + return telegramId; + } + + public MedicalHistory getMedicalHistory() { + return medicalHistory; + } + + public void setTelegramId(String telegramId) { + this.telegramId = telegramId; + } + + public void setMedicalHistory(MedicalHistory medicalHistory) { + this.medicalHistory = medicalHistory; + } + + /** + * Adds allergy into the medical history of patient + */ + public void addAllergy(String allergy) { + medicalHistory.addAllergy(new Allergy(allergy)); + } + + /** + * Adds condition into the medical history of patient + */ + public void addCondition(String condition) { + medicalHistory.addCondition(new Condition(condition)); + } + + /** + * Adds an upcoming appointment to the patient's queue of upcoming appointment. + */ + public void addUpcomingAppointment(Appointment appointment) { + upcomingAppointments.add(appointment); + } + + /** + * Deletes appointment from patient's queue of upcoming appointment. + */ + public void deleteAppointment(Appointment appointment) { + Appointment apptToBeDeleted = null; + for (Appointment appt : upcomingAppointments) { + if (appt.getAppointmentId() == appointment.getAppointmentId()) { + apptToBeDeleted = appt; + break; + } + } + upcomingAppointments.remove(apptToBeDeleted); + } + + /** + * Updates appointment from patient's queue of upcoming appointment. + * Currently only for prescription + */ + public void setAppointment(Appointment target, Appointment editedAppointment) { + int indexToBeDeleted = -1; + boolean inUpComingAppointments = false; + boolean inPastAppointments = false; + + for (Appointment appt : upcomingAppointments) { + if (appt.getAppointmentId() == target.getAppointmentId()) { + indexToBeDeleted = upcomingAppointments.indexOf(appt); + inUpComingAppointments = true; + break; + } + } + + if (indexToBeDeleted == -1 && !inUpComingAppointments) { + for (Appointment pastAppt : pastAppointments) { + if (pastAppt.getAppointmentId() == target.getAppointmentId()) { + indexToBeDeleted = pastAppointments.indexOf(pastAppt); + System.out.println(indexToBeDeleted); + inPastAppointments = true; + break; + } + } + } + if (inUpComingAppointments) { + upcomingAppointments.set(indexToBeDeleted, editedAppointment); + } else if (inPastAppointments) { + pastAppointments.set(indexToBeDeleted, editedAppointment); + } + } + + + /** + * Completes the {@code appointment} of the patient, placing the records of the appointment in to the stack of + * appointments + */ + public void completeUpcomingAppointment(Appointment targetAppointment) { + Appointment appointmentToRemove = null; + for (Appointment app : upcomingAppointments) { + if (app.isSameAppointment(targetAppointment)) { + appointmentToRemove = app; + } + } + appointmentToRemove.completeAppointment(); + upcomingAppointments.remove(appointmentToRemove); + pastAppointments.add(appointmentToRemove); + } + + /** + * Check if the patient contains a certain appointment by {@code appointmentId} + */ + public boolean hasAppointment(int appointmentId) { + for (Appointment app : upcomingAppointments) { + if (app.getAppointmentId() == appointmentId) { + return true; + } + } + + for (Appointment app : pastAppointments) { + if (app.getAppointmentId() == appointmentId) { + return true; + } + } + return false; + } + + /** + * Check if appointment clash with any upcoming appointments by {@code targetAppointment} + */ + public boolean hasClashForAppointment(Appointment appointmentToCheck) { + for (Appointment app : upcomingAppointments) { + if (app.hasClashAppointment(appointmentToCheck)) { + return true; // There is a clash in the appointments + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index a1409233ceb9..cad59fe7cb8a 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -56,3 +56,4 @@ public int hashCode() { } } + diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 9982393dabb5..d0b7e521f19c 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -48,7 +48,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.toLowerCase().equals(((Name) other).fullName.toLowerCase())); // state check } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 557a7a60cd51..aeaa531e6770 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -22,17 +22,19 @@ public class Person { // Data fields private final Address address; + private final Remark remark; private final Set tags = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address, Remark remark, Set tags) { + requireAllNonNull(name, phone, email, address, remark, tags); this.name = name; this.phone = phone; this.email = email; this.address = address; + this.remark = remark; this.tags.addAll(tags); } @@ -52,6 +54,10 @@ public Address getAddress() { return address; } + public Remark getRemark() { + return remark; + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -61,8 +67,7 @@ public Set getTags() { } /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. + * Returns true if both persons of the same name. */ public boolean isSamePerson(Person otherPerson) { if (otherPerson == this) { @@ -71,7 +76,7 @@ public boolean isSamePerson(Person otherPerson) { return otherPerson != null && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); + && otherPerson.getPhone().equals(getPhone()); } /** @@ -93,13 +98,14 @@ public boolean equals(Object other) { && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) + && otherPerson.getRemark().equals(getRemark()) && otherPerson.getTags().equals(getTags()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address, remark, tags); } @Override @@ -112,6 +118,8 @@ public String toString() { .append(getEmail()) .append(" Address: ") .append(getAddress()) + .append(" Remark: ") + .append(getRemark()) .append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/model/person/Remark.java b/src/main/java/seedu/address/model/person/Remark.java new file mode 100644 index 000000000000..107438fc4e55 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Remark.java @@ -0,0 +1,37 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's remark in the address book. + * Guarantees: immutable; + */ +public class Remark { + + public final String value; + + /** + * Constructs an {@code Remark}. + */ + public Remark(String remark) { + requireNonNull(remark); + value = remark; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Remark // instanceof handles nulls + && value.equals(((Remark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 5856aa42e6b5..8bda9be2793d 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -8,6 +8,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -95,6 +97,35 @@ public void setPersons(List persons) { internalList.setAll(persons); } + + /** + * Finds the patient in the list with {@code persons}. + * {@code patient} must be in the list of persons. + */ + public Patient findPatient(Patient patient) { + requireNonNull(patient); + for (Person p : internalList) { + if (p instanceof Patient && p.equals(patient)) { + return (Patient) p; + } + } + throw new PersonNotFoundException(); + } + + /** + * Finds the doctor in the list with {@code persons}. + * {@code doctor} must be in the list of persons. + */ + public Doctor findDoctor(Doctor doctor) { + requireNonNull(doctor); + for (Person d : internalList) { + if (d instanceof Doctor && d.equals(doctor)) { + return (Doctor) d; + } + } + throw new PersonNotFoundException(); + } + /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ diff --git a/src/main/java/seedu/address/model/tag/TagContainsDoctorPredicate.java b/src/main/java/seedu/address/model/tag/TagContainsDoctorPredicate.java new file mode 100644 index 000000000000..a96da3b5455c --- /dev/null +++ b/src/main/java/seedu/address/model/tag/TagContainsDoctorPredicate.java @@ -0,0 +1,26 @@ +package seedu.address.model.tag; + +import java.util.function.Predicate; + +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Tag} contains "Doctor". + */ +public class TagContainsDoctorPredicate implements Predicate { + + @Override + public boolean test(Person person) { + return person.getTags().contains(new Tag("Doctor")); + } + + @Override + public boolean equals(Object other) { + if (other == this) { //if same object + return true; + } else { + return false; + } + } + +} diff --git a/src/main/java/seedu/address/model/tag/TagContainsPatientPredicate.java b/src/main/java/seedu/address/model/tag/TagContainsPatientPredicate.java new file mode 100644 index 000000000000..59dd82e1b9dd --- /dev/null +++ b/src/main/java/seedu/address/model/tag/TagContainsPatientPredicate.java @@ -0,0 +1,25 @@ +package seedu.address.model.tag; + +import java.util.function.Predicate; + +import seedu.address.model.person.Person; +/** + * Tests that a {@code Person}'s {@code Tag} contains "Patient". + */ +public class TagContainsPatientPredicate implements Predicate { + + @Override + public boolean test(Person person) { + return person.getTags().contains(new Tag("Patient")); + } + + @Override + public boolean equals(Object other) { + if (other == this) { //if same object + return true; + } else { + return false; + } + } + +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..76b42abb7384 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -4,46 +4,47 @@ import java.util.Set; import java.util.stream.Collectors; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.HealthBook; +import seedu.address.model.ReadOnlyHealthBook; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; 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.person.Remark; import seedu.address.model.tag.Tag; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code HealthBook} with sample data. */ public class SampleDataUtil { + + public static final Remark EMPTY_REMARK = new Remark(""); + public static final Remark NON_EMPTY_REMARK = new Remark("Likes dogs"); + 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 Patient(new Name("Utah Tan"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), NON_EMPTY_REMARK, + getTagSet("Patient"), "123"), + new Patient(new Name("Jackie Chen"), new Phone("91234232"), new Email("jackiechen@example.com"), + new Address("Blk 123 Clementi Street 9, #08-20"), EMPTY_REMARK, + getTagSet("Patient"), "123"), }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); + public static ReadOnlyHealthBook getSampleHealthBook() { + HealthBook sampleAb = new HealthBook(); for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + if (samplePerson instanceof Patient) { + sampleAb.addPatient((Patient) samplePerson); + } else if (samplePerson instanceof Doctor) { + sampleAb.addDoctor((Doctor) samplePerson); + } else { + sampleAb.addPerson(samplePerson); + } } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/HealthBookStorage.java similarity index 51% rename from src/main/java/seedu/address/storage/AddressBookStorage.java rename to src/main/java/seedu/address/storage/HealthBookStorage.java index 4599182b3f92..e47ed7e8c183 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/HealthBookStorage.java @@ -5,12 +5,13 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.HealthBook; +import seedu.address.model.ReadOnlyHealthBook; /** - * Represents a storage for {@link seedu.address.model.AddressBook}. + * Represents a storage for {@link HealthBook}. */ -public interface AddressBookStorage { +public interface HealthBookStorage { /** * Returns the file path of the data file. @@ -18,28 +19,28 @@ public interface AddressBookStorage { Path getAddressBookFilePath(); /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. + * Returns HealthBook data as a {@link ReadOnlyHealthBook}. * Returns {@code Optional.empty()} if storage file is not found. * @throws DataConversionException if the data in storage is not in the expected format. * @throws IOException if there was any problem when reading from the storage. */ - Optional readAddressBook() throws DataConversionException, IOException; + Optional readAddressBook() throws DataConversionException, IOException; /** * @see #getAddressBookFilePath() */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; + Optional readAddressBook(Path filePath) throws DataConversionException, IOException; /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. + * Saves the given {@link ReadOnlyHealthBook} 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(ReadOnlyHealthBook addressBook) throws IOException; /** - * @see #saveAddressBook(ReadOnlyAddressBook) + * @see #saveAddressBook(ReadOnlyHealthBook) */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; + void saveAddressBook(ReadOnlyHealthBook addressBook, Path filePath) throws IOException; } diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 28791127999b..5f477fd77371 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -4,16 +4,16 @@ import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.HealthBookChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHealthBook; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends HealthBookStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -25,15 +25,15 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { Path getAddressBookFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readAddressBook() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveAddressBook(ReadOnlyHealthBook addressBook) throws IOException; /** * Saves the current version of the Address Book to the hard disk. * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); + void handleAddressBookChangedEvent(HealthBookChangedEvent abce); } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b0df908a76a7..614a629090bf 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -9,25 +9,24 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.HealthBookChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHealthBook; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of HealthBook data in local storage. */ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private HealthBookStorage healthBookStorage; private UserPrefsStorage userPrefsStorage; - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(HealthBookStorage healthBookStorage, UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.healthBookStorage = healthBookStorage; this.userPrefsStorage = userPrefsStorage; } @@ -49,39 +48,39 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ HealthBook methods ============================== @Override public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + return healthBookStorage.getAddressBookFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readAddressBook() throws DataConversionException, IOException { + return readAddressBook(healthBookStorage.getAddressBookFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return healthBookStorage.readAddressBook(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveAddressBook(ReadOnlyHealthBook addressBook) throws IOException { + saveAddressBook(addressBook, healthBookStorage.getAddressBookFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveAddressBook(ReadOnlyHealthBook addressBook, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + healthBookStorage.saveAddressBook(addressBook, filePath); } @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleAddressBookChangedEvent(HealthBookChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { saveAddressBook(event.data); diff --git a/src/main/java/seedu/address/storage/XmlAdaptedAllergy.java b/src/main/java/seedu/address/storage/XmlAdaptedAllergy.java new file mode 100644 index 000000000000..8aa9de0463ca --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedAllergy.java @@ -0,0 +1,76 @@ +package seedu.address.storage; + +import static seedu.address.model.patient.Allergy.MESSAGE_ALLERGY_CONSTRAINTS; +import static seedu.address.model.patient.Allergy.isValidAllergy; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.patient.Allergy; + +/** + * JAXB-friendly adapted version of the Allergy. + */ +public class XmlAdaptedAllergy { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Allergy's %s field is missing!"; + + @XmlValue + private String allergy; + + + + /** + * Constructs an XmlAdaptedAllergy. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAllergy(){} + + /** + * Constructs an XmlAdaptedAllergy. + * The argument is String. + */ + public XmlAdaptedAllergy(String allergy) { + this.allergy = allergy; + } + + /** + * Constructs an XmlAdaptedAllergy. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedAllergy(Allergy source) { + allergy = source.allergy; + } + + + public String getXmlAdaptedAllergy() { + return this.allergy; + } + + /** + * Converts this jaxb-friendly adapted tag object into the model's Tag object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Allergy toModelType() throws IllegalValueException { + if (!isValidAllergy(allergy)) { + throw new IllegalValueException(MESSAGE_ALLERGY_CONSTRAINTS); + } + return new Allergy(allergy); + + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTag)) { + return false; + } + + return allergy.equals(((XmlAdaptedAllergy) other).allergy); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java b/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java new file mode 100644 index 000000000000..ccd507028192 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java @@ -0,0 +1,141 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentId; +import seedu.address.model.appointment.Prescription; +import seedu.address.model.appointment.Status; +import seedu.address.model.doctor.Doctor; + +/** + * JAXB-friendly adapted version of the Appointment. + */ +public class XmlAdaptedAppointment { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment's %s field is missing!"; + + @XmlElement(required = true) + private int appointmentId; + @XmlElement(required = true) + private String doctor; + @XmlElement(required = true) + private String patient; + @XmlElement(required = true) + private String dateTime; + @XmlElement(required = true) + private String status; + @XmlElement + private String comments; + @XmlElement + private List prescriptions; + + /** + * Constructs an XmlAdaptedAppointment. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAppointment() {} + + /** + * Constructs a {@code XmlAdaptedAppointment} with the given {@code prescriptionName}. + */ + public XmlAdaptedAppointment(int appointmentId, String doctor, String patient, String dateTime, String status, + String comments, List prescriptions) { + this.appointmentId = appointmentId; + this.doctor = doctor; + this.patient = patient; + this.dateTime = dateTime; + this.status = status; + this.comments = comments; + this.prescriptions = prescriptions; + } + + /** + * Converts a given Appointment into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedAppointment(Appointment source) { + appointmentId = source.getAppointmentId(); + doctor = source.getDoctor(); + patient = source.getPatient(); + dateTime = source.getDateTime().toString(); + status = source.getStatus().name(); + comments = source.getComments(); + prescriptions = source.getPrescriptions().stream() + .map(XmlAdaptedPrescription::new) + .collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted Appointment object into the model's Appointment object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Appointment toModelType() throws IllegalValueException { + final List appointmentPrescriptions = new ArrayList<>(); + if (prescriptions != null && prescriptions.size() > 0) { + for (XmlAdaptedPrescription prescription : prescriptions) { + appointmentPrescriptions.add(prescription.toModelType()); + } + } + + if (appointmentId == 0) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + AppointmentId.class.getSimpleName())); + } + final AppointmentId currentAppointmentId = new AppointmentId(appointmentId); + + if (doctor == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Doctor.class.getSimpleName())); + } + + if (patient == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Doctor.class.getSimpleName())); + } + + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + LocalDateTime.class.getSimpleName())); + } + final LocalDateTime appointmentDateTime = LocalDateTime.parse(dateTime); + + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Status.class.getSimpleName())); + } + + Status appointmentStatus = Status.UPCOMING; + if (status.equals("COMPLETED")) { + appointmentStatus = Status.COMPLETED; + } + + return new Appointment(currentAppointmentId, doctor, patient, appointmentDateTime, + appointmentStatus, comments, appointmentPrescriptions); + } + + public int getAppointmentId() { + return appointmentId; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAppointment)) { + return false; + } + + return appointmentId == ((XmlAdaptedAppointment) other).getAppointmentId(); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedCondition.java b/src/main/java/seedu/address/storage/XmlAdaptedCondition.java new file mode 100644 index 000000000000..5fc731f3c858 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedCondition.java @@ -0,0 +1,71 @@ +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.patient.Condition; + +/** + * JAXB-friendly adapted version of the Condition. + */ +public class XmlAdaptedCondition { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Condition's %s field is missing!"; + + @XmlValue + private String condition; + /** + * Constructs an XmlAdaptedTag. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCondition() {} + + /** + * Constructs an XmlAdaptedCondition. + * The argument is String. + */ + public XmlAdaptedCondition(String condition) { + this.condition = condition; + } + + /** + * Constructs an XmlAdaptedTag. + * + * @param source future changes to this will not affect the created. + */ + public XmlAdaptedCondition(Condition source) { + condition = source.condition; + } + + + public String getXmlAdaptedCondition() { + return this.condition; + } + + + /** + * Converts this jaxb-friendly adapted tag object into the model's Tag object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Condition toModelType() throws IllegalValueException { + if (!Condition.isValidCondition(condition)) { + throw new IllegalValueException(Condition.MESSAGE_CONDITION_CONSTRAINTS); + } + return new Condition(condition); + + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTag)) { + return false; + } + + return condition.equals(((XmlAdaptedCondition) other).condition); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedMedicalHistory.java b/src/main/java/seedu/address/storage/XmlAdaptedMedicalHistory.java new file mode 100644 index 000000000000..fbbc1c95f3e0 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedMedicalHistory.java @@ -0,0 +1,93 @@ +package seedu.address.storage; + +import java.util.ArrayList; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.patient.MedicalHistory; + +/** + * JAXB-friendly adapted version of the MedicalHistory. + */ +public class XmlAdaptedMedicalHistory { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "MedicalHistory's %s field is missing!"; + + @XmlElement + private ArrayList allergies = new ArrayList<>(); + @XmlElement + private ArrayList conditions = new ArrayList<>(); + + + /** + * Constructs an XmlAdaptedMedicalHistory. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedMedicalHistory() {} + + public XmlAdaptedMedicalHistory(ArrayList allergies, ArrayList conditions) { + this.allergies = allergies; + this.conditions = conditions; + } + + public XmlAdaptedMedicalHistory(MedicalHistory source) { + for (int i = 0; i < source.getAllergies().size(); i++) { + this.allergies.add(new XmlAdaptedAllergy(source.getAllergies().get(i))); + } + for (int i = 0; i < source.getConditions().size(); i++) { + this.conditions.add(new XmlAdaptedCondition(source.getConditions().get(i))); + } + } + + + /** + * Converts this jaxb-friendly adapted MedicalHistory object into the model's MedicalHistory object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public MedicalHistory toModelType() throws IllegalValueException { + + ArrayList al = new ArrayList<>(); + ArrayList c = new ArrayList<>(); + + for (int i = 0; i < allergies.size(); i++) { + al.add(this.allergies.get(i).toModelType()); + } + + for (int i = 0; i < conditions.size(); i++) { + c.add(this.conditions.get(i).toModelType()); + } + + return new MedicalHistory(al, c); + } + + + public ArrayList getAllergies() { + return allergies; + } + + public ArrayList getConditions() { + return conditions; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedMedicalHistory)) { + return false; + } + + if (allergies.equals(((XmlAdaptedMedicalHistory) other).getAllergies()) + && conditions.equals(((XmlAdaptedMedicalHistory) other).getConditions())) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java index c03785e5700f..5e67c8e99f00 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java @@ -10,11 +10,16 @@ import javax.xml.bind.annotation.XmlElement; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.MedicalHistory; +import seedu.address.model.patient.Patient; 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.person.Remark; import seedu.address.model.tag.Tag; /** @@ -32,9 +37,17 @@ public class XmlAdaptedPerson { private String email; @XmlElement(required = true) private String address; - + @XmlElement(required = true) + private String remark; @XmlElement private List tagged = new ArrayList<>(); + @XmlElement + private List upcomingAppointments = new ArrayList<>(); + @XmlElement + private List pastAppointments = new ArrayList<>(); + @XmlElement + private XmlAdaptedMedicalHistory medicalHistory = new XmlAdaptedMedicalHistory(); + /** * Constructs an XmlAdaptedPerson. @@ -45,14 +58,47 @@ public XmlAdaptedPerson() {} /** * Constructs an {@code XmlAdaptedPerson} with the given person details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + public XmlAdaptedPerson(String name, String phone, String email, String address, + String remark, List tagged, List upcomingAppointments, + List pastAppointments) { + + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.remark = remark; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + if (upcomingAppointments != null) { + this.upcomingAppointments = new ArrayList<>(upcomingAppointments); + } + if (pastAppointments != null) { + this.pastAppointments = new ArrayList<>(pastAppointments); + } + + } + public XmlAdaptedPerson(String name, String phone, String email, String address, + String remark, List tagged, List upcomingAppointments, + List pastAppointments, XmlAdaptedMedicalHistory medicalHistory) { + this.name = name; this.phone = phone; this.email = email; this.address = address; + this.remark = remark; if (tagged != null) { this.tagged = new ArrayList<>(tagged); } + if (upcomingAppointments != null) { + this.upcomingAppointments = new ArrayList<>(upcomingAppointments); + } + if (pastAppointments != null) { + this.pastAppointments = new ArrayList<>(pastAppointments); + } + if (medicalHistory != null) { + this.medicalHistory = medicalHistory; + } } /** @@ -65,9 +111,24 @@ public XmlAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + remark = source.getRemark().value; tagged = source.getTags().stream() .map(XmlAdaptedTag::new) .collect(Collectors.toList()); + + if (!tagged.isEmpty() && tagged.get(0).equals(new XmlAdaptedTag("Patient"))) { + upcomingAppointments = ((Patient) source).getUpcomingAppointments().stream() + .map(XmlAdaptedAppointment::new) + .collect(Collectors.toList()); + pastAppointments = ((Patient) source).getPastAppointments().stream() + .map(XmlAdaptedAppointment::new) + .collect(Collectors.toList()); + medicalHistory = new XmlAdaptedMedicalHistory(((Patient) source).getMedicalHistory()); + } else if (!tagged.isEmpty() && tagged.get(0).equals(new XmlAdaptedTag("Doctor"))) { + upcomingAppointments = ((Doctor) source).getUpcomingAppointments().stream() + .map(XmlAdaptedAppointment::new) + .collect(Collectors.toList()); + } } /** @@ -81,6 +142,19 @@ public Person toModelType() throws IllegalValueException { personTags.add(tag.toModelType()); } + final List allUpcomingAppointments = new ArrayList<>(); + for (XmlAdaptedAppointment upcomingAppointment : upcomingAppointments) { + allUpcomingAppointments.add(upcomingAppointment.toModelType()); + } + + final List allPastAppointments = new ArrayList<>(); + for (XmlAdaptedAppointment pastAppointments : pastAppointments) { + allPastAppointments.add(pastAppointments.toModelType()); + } + + MedicalHistory modelMedicalHistory = medicalHistory.toModelType(); + + if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -113,8 +187,22 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); + if (remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); + } + final Remark modelRemark = new Remark(remark); + final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + if (!modelTags.isEmpty() && modelTags.toArray()[0].equals(new Tag("Doctor"))) { + return new Doctor(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags, + allUpcomingAppointments); + } else if (!modelTags.isEmpty() && modelTags.toArray()[0].equals(new Tag("Patient"))) { + return new Patient(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags, "123", + allUpcomingAppointments, allPastAppointments, modelMedicalHistory); + } else { + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags); + } + } @Override @@ -132,6 +220,7 @@ public boolean equals(Object other) { && Objects.equals(phone, otherPerson.phone) && Objects.equals(email, otherPerson.email) && Objects.equals(address, otherPerson.address) + && Objects.equals(remark, otherPerson.remark) && tagged.equals(otherPerson.tagged); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPrescription.java b/src/main/java/seedu/address/storage/XmlAdaptedPrescription.java new file mode 100644 index 000000000000..4b79687033a4 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedPrescription.java @@ -0,0 +1,125 @@ +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.AppointmentId; +import seedu.address.model.appointment.ConsumptionPerDay; +import seedu.address.model.appointment.Dosage; +import seedu.address.model.appointment.MedicineName; +import seedu.address.model.appointment.Prescription; + +/** + * JAXB-friendly adapted version of the Prescription. + */ +public class XmlAdaptedPrescription { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Prescription's %s field is missing!"; + + @XmlElement(required = true) + private int appointmentId; + @XmlElement(required = true) + private String medicineName; + @XmlElement(required = true) + private String dosage; + @XmlElement(required = true) + private String consumptionPerDay; + + /** + * Constructs an XmlAdaptedPrescription. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedPrescription() {} + + /** + * Constructs a {@code XmlAdaptedPrescription} with the given prescription details + */ + public XmlAdaptedPrescription(int appointmentId, String medicineName, String dosage, String consumptionPerDay) { + this.appointmentId = appointmentId; + this.medicineName = medicineName; + this.dosage = dosage; + this.consumptionPerDay = consumptionPerDay; + } + + /** + * Converts a given Prescription into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedPrescription(Prescription source) { + appointmentId = source.getId(); + medicineName = source.getMedicineName().getFullMedicineName(); + dosage = source.getDosage().getValue(); + consumptionPerDay = source.getConsumptionPerDay().getValue(); + } + + /** + * Converts this jaxb-friendly adapted Prescription object into the model's Prescription object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Prescription toModelType() throws IllegalValueException { + + if (appointmentId == 0) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + AppointmentId.class.getSimpleName())); + } + final int medAppointmentId = appointmentId; + + if (medicineName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + MedicineName.class.getSimpleName())); + } + final MedicineName medName = new MedicineName(medicineName); + + + if (dosage == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Dosage.class.getSimpleName())); + } + final Dosage medDosage = new Dosage(dosage); + + if (consumptionPerDay == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ConsumptionPerDay.class.getSimpleName())); + } + final ConsumptionPerDay medConsumptionPerDay = new ConsumptionPerDay(consumptionPerDay); + + return new Prescription(medAppointmentId, medName, medDosage, medConsumptionPerDay); + } + + public int getAppointmentId() { + return appointmentId; + } + + public String getMedicineName() { + return medicineName; + } + + public String getDosage() { + return dosage; + } + + public String getConsumptionPerDay() { + return consumptionPerDay; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedPrescription)) { + return false; + } + + if (medicineName.equals(((XmlAdaptedPrescription) other).getMedicineName()) + && dosage.equals(((XmlAdaptedPrescription) other).getDosage()) + && consumptionPerDay == (((XmlAdaptedPrescription) other).getConsumptionPerDay())) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index d8f65dc036ab..469c199815d8 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -15,7 +15,7 @@ public class XmlFileStorage { /** * Saves the given addressbook data to the specified file. */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(Path file, XmlSerializableHealthBook addressBook) throws FileNotFoundException { try { XmlUtil.saveDataToFile(file, addressBook); @@ -27,10 +27,10 @@ public static void saveDataToFile(Path file, XmlSerializableAddressBook addressB /** * Returns address book in the file or an empty address book */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, + public static XmlSerializableHealthBook loadDataFromSaveFile(Path file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableHealthBook.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlHealthBookStorage.java similarity index 65% rename from src/main/java/seedu/address/storage/XmlAddressBookStorage.java rename to src/main/java/seedu/address/storage/XmlHealthBookStorage.java index ecf0e7ec23a8..e1b7ce30ac86 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlHealthBookStorage.java @@ -13,18 +13,18 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHealthBook; /** - * A class to access AddressBook data stored as an xml file on the hard disk. + * A class to access HealthBook data stored as an xml file on the hard disk. */ -public class XmlAddressBookStorage implements AddressBookStorage { +public class XmlHealthBookStorage implements HealthBookStorage { - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(XmlHealthBookStorage.class); private Path filePath; - public XmlAddressBookStorage(Path filePath) { + public XmlHealthBookStorage(Path filePath) { this.filePath = filePath; } @@ -33,7 +33,7 @@ public Path getAddressBookFilePath() { } @Override - public Optional readAddressBook() throws DataConversionException, IOException { + public Optional readAddressBook() throws DataConversionException, IOException { return readAddressBook(filePath); } @@ -42,16 +42,16 @@ public Optional readAddressBook() throws DataConversionExce * @param filePath location of the data. Cannot be null * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException, + public Optional readAddressBook(Path filePath) throws DataConversionException, FileNotFoundException { requireNonNull(filePath); if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); + logger.info("HealthBook file " + filePath + " not found"); return Optional.empty(); } - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); + XmlSerializableHealthBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); try { return Optional.of(xmlAddressBook.toModelType()); } catch (IllegalValueException ive) { @@ -61,20 +61,20 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + public void saveAddressBook(ReadOnlyHealthBook addressBook) throws IOException { saveAddressBook(addressBook, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * Similar to {@link #saveAddressBook(ReadOnlyHealthBook)} * @param filePath location of the data. Cannot be null */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveAddressBook(ReadOnlyHealthBook addressBook, Path filePath) throws IOException { requireNonNull(addressBook); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); + XmlFileStorage.saveDataToFile(filePath, new XmlSerializableHealthBook(addressBook)); } } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b85fa4a8f07e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - @XmlElement - private List persons; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - return persons.equals(((XmlSerializableAddressBook) other).persons); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableHealthBook.java b/src/main/java/seedu/address/storage/XmlSerializableHealthBook.java new file mode 100644 index 000000000000..a4803d9e24d3 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlSerializableHealthBook.java @@ -0,0 +1,95 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.HealthBook; +import seedu.address.model.ReadOnlyHealthBook; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * An Immutable HealthBook that is serializable to XML format + */ +@XmlRootElement(name = "addressbook") +public class XmlSerializableHealthBook { + + public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + + @XmlElement + private List persons; + @XmlElement + private List appointments; + @XmlElement + private int appointmentCounter; + + /** + * Creates an empty XmlSerializableHealthBook. + * This empty constructor is required for marshalling. + */ + public XmlSerializableHealthBook() { + persons = new ArrayList<>(); + appointments = new ArrayList<>(); + appointmentCounter = 10000; + } + + /** + * Conversion + */ + public XmlSerializableHealthBook(ReadOnlyHealthBook src) { + this(); + persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + appointments.addAll(src.getAppointmentList().stream().map(XmlAdaptedAppointment::new) + .collect(Collectors.toList())); + appointmentCounter = src.getAppointmentCounter(); + } + + /** + * Converts this addressbook into the model's {@code HealthBook} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedPerson}. + */ + public HealthBook toModelType() throws IllegalValueException { + HealthBook healthBook = new HealthBook(); + for (XmlAdaptedPerson p : persons) { + Person person = p.toModelType(); + if (healthBook.hasPerson(person)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + if (!person.getTags().isEmpty() && person.getTags().toArray()[0].equals(new Tag("Doctor"))) { + healthBook.addDoctor((Doctor) person); + } else if (!person.getTags().isEmpty() && person.getTags().toArray()[0].equals(new Tag("Patient"))) { + healthBook.addPatient((Patient) person); + } else { + healthBook.addPerson(person); + } + } + for (XmlAdaptedAppointment a : appointments) { + Appointment appointment = a.toModelType(); + healthBook.addAppointment(appointment); + } + healthBook.setAppointmentCounter(appointmentCounter); + return healthBook; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableHealthBook)) { + return false; + } + return persons.equals(((XmlSerializableHealthBook) other).persons); + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 3d7aaded5640..b74a69134d68 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,5 +1,8 @@ package seedu.address.ui; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -25,6 +28,13 @@ public class CommandBox extends UiPart { private final Logger logger = LogsCenter.getLogger(CommandBox.class); private final Logic logic; + private final List commands = new ArrayList<>(Arrays.asList( + "clear", "delete-patient", "delete-doctor", "exit", + "find", "help", "history", "list", "redo", "register-patient", "register-doctor", + "select", "undo", "add-appointment", "edit-appointment", "delete-appointment", "remark", + "add-medical-history", "filter-patient", "filter-doctor", "delete-medical-history", + "view-patient", "view-doctor", "complete-appointment", "add-prescription", + "delete-prescription")); private ListElementPointer historySnapshot; @FXML @@ -48,13 +58,16 @@ private void handleKeyPress(KeyEvent keyEvent) { // As up and down buttons will alter the position of the caret, // consuming it causes the caret's position to remain unchanged keyEvent.consume(); - navigateToPreviousInput(); break; case DOWN: keyEvent.consume(); navigateToNextInput(); break; + case TAB: + keyEvent.consume(); + filterCommands(commandTextField.getText()); + break; default: // let JavaFx handle the keypress } @@ -86,6 +99,20 @@ private void navigateToNextInput() { replaceText(historySnapshot.next()); } + /** + * Filter list of commands to find nearest command + * to the given input of at least one character + */ + private void filterCommands(String text) { + String nearestCommand = commands.stream() + .filter(command -> command.startsWith(text)) + .findFirst() + .orElse("No commands beginning with '" + text + "'"); + replaceText(nearestCommand); + commandTextField.requestFocus(); + commandTextField.end(); + } + /** * Sets {@code CommandBox}'s text field with {@code text} and * positions the caret to the end of the {@code text}. diff --git a/src/main/java/seedu/address/ui/InformationPanel.java b/src/main/java/seedu/address/ui/InformationPanel.java new file mode 100644 index 000000000000..ecc831d36957 --- /dev/null +++ b/src/main/java/seedu/address/ui/InformationPanel.java @@ -0,0 +1,253 @@ +package seedu.address.ui; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.Prescription; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Allergy; +import seedu.address.model.patient.Condition; +import seedu.address.model.patient.Patient; +import seedu.address.model.person.Person; + +/** + * The Information Panel of the App. + */ +public class InformationPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Patient patient; + private Doctor doctor; + + @FXML + private Label allergiesLabel; + + @FXML + private Label conditionsLabel; + + @FXML + private TableView upcomingAppointmentTable; + + @FXML + private TableView pastAppointmentTable; + + @FXML + private TableColumn> prescriptionColumn; + + @FXML + private TableColumn> prescriptionPastColumn; + + @FXML + private TableColumn dateTimeColumn; + + @FXML + private TableColumn dateTimePastColumn; + + public InformationPanel(String fxmlPath) { + super(fxmlPath); + this.patient = null; + registerAsAnEventHandler(this); + } + + private void setConnections() { + upcomingAppointmentTable.getItems().clear(); + pastAppointmentTable.getItems().clear(); + allergiesLabel.setText(" "); + conditionsLabel.setText(" "); + } + + private void setConnections(ObservableList upcomingAppointmentList) { + upcomingAppointmentTable.getItems().clear(); + upcomingAppointmentTable.getItems().addAll(upcomingAppointmentList); + dateTimeColumn.setCellFactory(column -> { + return new TableCell() { + @Override + protected void updateItem(LocalDateTime item, boolean empty) { + super.updateItem(item, empty); + + if (item == null || empty) { + setText(null); + } else { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + final Text text = new Text(item.format(formatter)); + setGraphic(text); + if (item.isBefore(LocalDateTime.now())) { + text.setFill(Color.RED); + } else { + text.setFill(Color.WHITE); + } + } + } + }; + }); + } + + private void setConnections(ObservableList allergiesList, + ObservableList conditionsList, + ObservableList upcomingAppointmentList, + ObservableList pastAppointmentList) { + + String parsedAllergiesList = allergiesList.toString().substring(1, allergiesList.toString().length() - 1); + String parsedConditionsList = conditionsList.toString().substring(1, conditionsList.toString().length() - 1); + allergiesLabel.setText(parsedAllergiesList); + conditionsLabel.setText(parsedConditionsList); + upcomingAppointmentTable.getItems().clear(); + pastAppointmentTable.getItems().clear(); + upcomingAppointmentTable.getItems().addAll(upcomingAppointmentList); + pastAppointmentTable.getItems().addAll(pastAppointmentList); + prescriptionColumn.setCellFactory(column -> { + return new TableCell>() { + @Override + protected void updateItem(ArrayList item, boolean empty) { + super.updateItem(item, empty); + + if (item == null || empty) { + setGraphic(null); + return; + } else { + final Text text = new Text(item.toString() + .substring(1, item.toString().length() - 1) + .replaceAll(", ", "\n")); + text.setWrappingWidth(500.0); + setPrefHeight(text.getLayoutBounds().getHeight()); + setGraphic(text); + text.setFill(Color.WHITE); + } + } + }; + }); + prescriptionPastColumn.setCellFactory(column -> { + return new TableCell>() { + @Override + protected void updateItem(ArrayList item, boolean empty) { + super.updateItem(item, empty); + + if (item == null || empty) { + setGraphic(null); + return; + } else { + final Text text = new Text(item.toString() + .substring(1, item.toString().length() - 1) + .replaceAll(", ", "\n")); + text.setWrappingWidth(500.0); + setPrefHeight(text.getLayoutBounds().getHeight()); + setGraphic(text); + text.setFill(Color.WHITE); + } + } + }; + }); + dateTimeColumn.setCellFactory(column -> { + return new TableCell() { + @Override + protected void updateItem(LocalDateTime item, boolean empty) { + super.updateItem(item, empty); + + if (item == null || empty) { + setText(null); + } else { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + final Text text = new Text(item.format(formatter)); + setGraphic(text); + if (item.isBefore(LocalDateTime.now())) { + text.setFill(Color.RED); + } else { + text.setFill(Color.WHITE); + } + } + } + }; + }); + dateTimePastColumn.setCellFactory(column -> { + return new TableCell() { + @Override + protected void updateItem(LocalDateTime item, boolean empty) { + super.updateItem(item, empty); + + if (item == null || empty) { + setText(null); + } else { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + setText(item.format(formatter)); + } + } + }; + }); + } + + /** + * Loads a patient's information on the panel. + */ + private void loadPatientInformation(Patient patient) { + Iterator allergiesItr = patient.getMedicalHistory().getAllergies().iterator(); + Iterator conditionsItr = patient.getMedicalHistory().getConditions().iterator(); + Iterator upcomingAppointmentItr = patient.getUpcomingAppointments().iterator(); + Iterator pastAppointmentItr = patient.getPastAppointments().iterator(); + + ObservableList allergiesList = FXCollections.observableArrayList(); + ObservableList conditionsList = FXCollections.observableArrayList(); + ObservableList upcomingAppointmentList = FXCollections.observableArrayList(); + ObservableList pastAppointmentList = FXCollections.observableArrayList(); + + while (allergiesItr.hasNext()) { + allergiesList.add(allergiesItr.next()); + } + while (conditionsItr.hasNext()) { + conditionsList.add(conditionsItr.next()); + } + while (upcomingAppointmentItr.hasNext()) { + upcomingAppointmentList.add(upcomingAppointmentItr.next()); + } + while (pastAppointmentItr.hasNext()) { + pastAppointmentList.add(pastAppointmentItr.next()); + } + setConnections(allergiesList, conditionsList, upcomingAppointmentList, pastAppointmentList); + } + + /** + * Loads a doctor's information on the panel. + */ + private void loadDoctorInformation(Doctor doctor) { + Iterator upcomingAppointmentItr = doctor.getUpcomingAppointments().iterator(); + ObservableList upcomingAppointmentList = FXCollections.observableArrayList(); + while (upcomingAppointmentItr.hasNext()) { + upcomingAppointmentList.add(upcomingAppointmentItr.next()); + } + setConnections(upcomingAppointmentList); + } + + /** + * Clear information on the panel. + */ + private void clearInformation() { + setConnections(); + } + + /** + * Changes panel between doctor and patient. + */ + public void changePanel(Person person) { + if (person instanceof Patient) { + loadPatientInformation((Patient) person); + } else if (person instanceof Doctor) { + loadDoctorInformation((Doctor) person); + } else { + clearInformation(); + } + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..14da6693a545 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,5 +1,6 @@ package seedu.address.ui; +import java.io.IOException; import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; @@ -16,9 +17,12 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; import seedu.address.commons.events.ui.ShowHelpRequestEvent; import seedu.address.logic.Logic; import seedu.address.model.UserPrefs; +import seedu.address.model.doctor.Doctor; +import seedu.address.model.patient.Patient; /** * The Main Window. Provides the basic application layout containing @@ -34,14 +38,14 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; + private InformationPanel informationPanel; private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; private HelpWindow helpWindow; @FXML - private StackPane browserPlaceholder; + private StackPane informationPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -119,8 +123,8 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + informationPanel = new InformationPanel("InformationPanel.fxml"); + informationPlaceholder.getChildren().add(informationPanel.getRoot()); personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); @@ -128,7 +132,7 @@ void fillInnerParts() { ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getHealthBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -191,13 +195,25 @@ public PersonListPanel getPersonListPanel() { return personListPanel; } - void releaseResources() { - browserPanel.freeResources(); - } - @Subscribe private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + @Subscribe + private void handleInformationPanelChangedEvent(PersonPanelSelectionChangedEvent event) throws IOException { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (event.getNewSelection() instanceof Patient) { + informationPanel = new InformationPanel("InformationPanel.fxml"); + informationPlaceholder.getChildren().add(informationPanel.getRoot()); + informationPanel.changePanel(event.getNewSelection()); + } else if (event.getNewSelection() instanceof Doctor) { + informationPanel = new InformationPanel("InformationPanelDoctor.fxml"); + informationPlaceholder.getChildren().add(informationPanel.getRoot()); + informationPanel.changePanel(event.getNewSelection()); + } else { + informationPanel.changePanel(null); + } + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..1a1144152337 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,5 +1,7 @@ package seedu.address.ui; +import java.util.Map; + import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; @@ -14,12 +16,15 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; + private static final Map COLORS = Map.of("Doctor", "178c05", + "Patient", "bb3e3b"); + /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. * As a consequence, UI elements' variable names cannot be set to such keywords * or an exception will be thrown by JavaFX during runtime. * - * @see The issue on AddressBook level 4 + * @see The issue on HealthBook level 4 */ public final Person person; @@ -37,6 +42,8 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML + private Label remark; + @FXML private FlowPane tags; public PersonCard(Person person, int displayedIndex) { @@ -47,7 +54,15 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + remark.setText(person.getRemark().value); + person.getTags().forEach(tag -> { + String tagName = tag.tagName; + tags.getChildren().add(new Label(tagName)); + if (COLORS.containsKey(tagName)) { + tags.getChildren().get(tags.getChildren().size() - 1).setStyle("-fx-background-color: #" + + COLORS.get(tagName)); + } + }); } @Override diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..9bd2e5bf083a 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -3,7 +3,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Clock; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.logging.Logger; import org.controlsfx.control.StatusBar; @@ -14,7 +15,7 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.HealthBookChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -74,9 +75,9 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { - long now = clock.millis(); - String lastUpdated = new Date(now).toString(); + public void handleAddressBookChangedEvent(HealthBookChangedEvent abce) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + String lastUpdated = LocalDateTime.now().format(formatter); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..dbf223a63a21 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -66,7 +66,7 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); + // mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/resources/credentials.json b/src/main/resources/credentials.json new file mode 100644 index 000000000000..cc0811c8deef --- /dev/null +++ b/src/main/resources/credentials.json @@ -0,0 +1,14 @@ +{ + "installed": { + "client_id": "442385750857-45n66io01ulubuks382olsprm3fvc5ur.apps.googleusercontent.com", + "project_id": "healthbook-1540278069038", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://www.googleapis.com/oauth2/v3/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "cNgVVLhc_6aunolIqNSwymsI", + "redirect_uris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost" + ] + } +} diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..935b9c9bdc61 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -39,10 +39,14 @@ -fx-max-height: 0; } +.table-cell { + -fx-background-color: derive(#1d1d1d, 30%); +} + .table-view { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-background-color: derive(#1d1d1d, 30%); -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -65,7 +69,7 @@ } .table-view .column-header .label { - -fx-font-size: 20pt; + -fx-font-size: 10pt; -fx-font-family: "Segoe UI Light"; -fx-text-fill: white; -fx-alignment: center-left; @@ -90,7 +94,7 @@ .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#1d1d1d,30%); } .list-cell { @@ -306,6 +310,16 @@ -fx-padding: 8 1 8 1; } +.titled-pane { + -fx-font-size: 10px; + -fx-font-weight: bold; + -fx-color: derive(#1d1d1d, 20%); +} + +.titled-pane > .title { + -fx-color: derive(#1d1d1d, 20%); +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; diff --git a/src/main/resources/view/InformationPanel.fxml b/src/main/resources/view/InformationPanel.fxml new file mode 100644 index 000000000000..72a6916a61a6 --- /dev/null +++ b/src/main/resources/view/InformationPanel.fxml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/InformationPanelDoctor.fxml b/src/main/resources/view/InformationPanelDoctor.fxml new file mode 100644 index 000000000000..0532a11cabbe --- /dev/null +++ b/src/main/resources/view/InformationPanelDoctor.fxml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index daf386d8f5b8..9f008b82452f 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -54,7 +54,7 @@ - + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..d1a7eb614f83 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -31,6 +31,7 @@

4th street
+ @@ -13,5 +14,6 @@ 948asdf2424 hans@example.com
4th street
+
diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlHealthBookStorageTest/invalidPersonHealthBook.xml similarity index 91% rename from src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml rename to src/test/data/XmlHealthBookStorageTest/invalidPersonHealthBook.xml index cfa128e72828..e14f38fdff44 100644 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ b/src/test/data/XmlHealthBookStorageTest/invalidPersonHealthBook.xml @@ -1,5 +1,5 @@ - + Ha!ns Mu@ster @@ -7,4 +7,4 @@ hans@example.com
4th street
-
+ diff --git a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml b/src/test/data/XmlSerializableHealthBookTest/duplicatePersonHealthBook.xml similarity index 91% rename from src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml rename to src/test/data/XmlSerializableHealthBookTest/duplicatePersonHealthBook.xml index ac02230263d3..2c71414eb147 100644 --- a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml +++ b/src/test/data/XmlSerializableHealthBookTest/duplicatePersonHealthBook.xml @@ -6,6 +6,7 @@ 94351253 alice@example.com
123, Jurong West Ave 6, #08-111
+ friends @@ -14,6 +15,7 @@ Alice Pauline 94351253 pauline@example.com +
4th street
diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml b/src/test/data/XmlSerializableHealthBookTest/invalidPersonHealthBook.xml similarity index 100% rename from src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml rename to src/test/data/XmlSerializableHealthBookTest/invalidPersonHealthBook.xml diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableHealthBookTest/typicalPersonsHealthBook.xml similarity index 83% rename from src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml rename to src/test/data/XmlSerializableHealthBookTest/typicalPersonsHealthBook.xml index d812b05e32bb..64c1fe4b51a1 100644 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ b/src/test/data/XmlSerializableHealthBookTest/typicalPersonsHealthBook.xml @@ -1,11 +1,12 @@ - + Alice Pauline 94351253 alice@example.com
123, Jurong West Ave 6, #08-111
+ friends
@@ -13,6 +14,7 @@ 98765432 johnd@example.com
311, Clementi Ave 2, #02-25
+ owesMoney friends
@@ -21,12 +23,14 @@ 95352563 heinz@example.com
wall street
+ Daniel Meier 87652533 cornelia@example.com
10th street
+ friends
@@ -34,17 +38,20 @@ 9482224 werner@example.com
michegan ave
+
Fiona Kunz 9482427 lydia@example.com
little tokyo
+
George Best 9482442 anna@example.com
4th street
+
diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml index ba49c971e884..58084e96169a 100644 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ b/src/test/data/XmlUtilTest/invalidPersonField.xml @@ -5,5 +5,6 @@ 9482asf424 hans@example
4th street
+ friends
diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..c3777ba807d1 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -4,5 +4,6 @@ 9482424 hans@example
4th street
+ friends
diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempHealthBook.xml similarity index 100% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempHealthBook.xml diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validHealthBook.xml similarity index 84% rename from src/test/data/XmlUtilTest/validAddressBook.xml rename to src/test/data/XmlUtilTest/validHealthBook.xml index 6265778674d3..addc8a24d525 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validHealthBook.xml @@ -5,53 +5,62 @@ 9482424 hans@example.com
4th street
+ Ruth Mueller 87249245 ruth@example.com
81th street
+
Heinz Kurz 95352563 heinz@example.com
wall street
+
Cornelia Meier 87652533 cornelia@example.com
10th street
+
Werner Meyer 9482224 werner@example.com
michegan ave
+
Lydia Kunz 9482427 lydia@example.com
little tokyo
+
Anna Best 9482442 anna@example.com
4th street
+
Stefan Meier 8482424 stefan@example.com
little india
+
Martin Mueller 8482131 hans@example.com
chicago ave
+
diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..9ace80ccfc54 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -4,5 +4,6 @@ 9482424 hans@example
4th street
+ friends
diff --git a/src/test/java/guitests/guihandles/InformationPanelHandle.java b/src/test/java/guitests/guihandles/InformationPanelHandle.java new file mode 100644 index 000000000000..5ce907b247fb --- /dev/null +++ b/src/test/java/guitests/guihandles/InformationPanelHandle.java @@ -0,0 +1,15 @@ +package guitests.guihandles; + +import javafx.scene.Node; + +/** + * A handler for the {@code InformationPanel} of the UI. + */ +public class InformationPanelHandle extends NodeHandle { + + public static final String INFORMATION_ID = "#information"; + + public InformationPanelHandle(Node informationPanelNode) { + super(informationPanelNode); + } +} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..8c3ad26f5528 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -12,7 +12,8 @@ public class MainWindowHandle extends StageHandle { private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; + // private final BrowserPanelHandle browserPanel; + private final InformationPanelHandle informationPanel; public MainWindowHandle(Stage stage) { super(stage); @@ -22,7 +23,8 @@ public MainWindowHandle(Stage stage) { commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + // browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + informationPanel = new InformationPanelHandle(getChildNode(InformationPanelHandle.INFORMATION_ID)); } public PersonListPanelHandle getPersonListPanel() { @@ -45,7 +47,11 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; + // public BrowserPanelHandle getBrowserPanel() { + // return browserPanel; + // } + + public InformationPanelHandle getInformationPanel() { + return informationPanel; } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index 1789735e49a8..7e9e1b5e77bc 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -19,6 +19,7 @@ public class PersonCardHandle extends NodeHandle { private static final String ADDRESS_FIELD_ID = "#address"; private static final String PHONE_FIELD_ID = "#phone"; private static final String EMAIL_FIELD_ID = "#email"; + private static final String REMARK_FIELD_ID = "#remark"; private static final String TAGS_FIELD_ID = "#tags"; private final Label idLabel; @@ -26,6 +27,7 @@ public class PersonCardHandle extends NodeHandle { private final Label addressLabel; private final Label phoneLabel; private final Label emailLabel; + private final Label remarkLabel; private final List