diff --git a/build.gradle b/build.gradle index 5cd2de53441..b12618d46a5 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'MediLink.jar' } defaultTasks 'clean', 'test' diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 3c4d14fab90..204a91bde68 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -129,7 +129,7 @@ Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete NRIC")` API call as an example. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) @@ -618,6 +618,39 @@ otherwise) Use case resumes at step 2. +* 4b. The edited fields include invalid inputs. + + * 4b1. Medilink Contacts shows an error message. + + Use case resumes at step 2. + +**Use case: UC5 - Undo a command** + +**MSS** + +1. User requests to delete a specific person in the list. +2. Medilink Contacts deletes the person. +3. User realises mistake, requests to undo previous action. +4. Medilink Contacts reverts to state before patient was deleted. + + Use case ends. + +**Extensions** + +* 4a. User wants to redo the command. + + * 4a1. User requests to redo last command. + * 4a2. MediLink Contacts reverts to state where patient was deleted. + + Use case ends. + +* 4b. User wants to perform another undo when there are no further actions to be undone. + + * 4b1. User requests to undo again. + * 4b2. Medilink Contacts shows an error message. + + Use case ends. + ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 05a56e0c57c..26b53b567fd 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,31 +3,32 @@ layout: page title: User Guide --- -MediLink Contacts(MLC) is a **desktop app for managing patients and doctors details, optimized for use via a Command +MediLink Contacts (MLC) is a **desktop app for managing patients and doctors details, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, MLC can get your patients management tasks done faster than traditional GUI apps. ### Table of Contents * Table of Contents - {:toc} +{:toc} -------------------------------------------------------------------------------------------------------------------- ## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. Ensure you have Java `11` or above installed in your Computer. If you don't, install it for your relevant operating + system at this link https://www.oracle.com/sg/java/technologies/javase/jdk11-archive-downloads.html -1. Download the latest `mediLink.jar` from [here](https://github.com/AY2324S1-CS2103T-T09-3/tp/releases). +2. Download the latest `MediLink.jar` from [here](https://github.com/AY2324S1-CS2103T-T09-3/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your MLC. +3. Copy the file to the folder you want to use as the _home folder_ for your MLC. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar medilink.jar` command +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar MediLink.jar` command to run the application.
A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: @@ -42,9 +43,26 @@ can get your patients management tasks done faster than traditional GUI apps. * `exit` : Exits the app. -1. Refer to the [Features](#features) below for details of each command. +6. Refer to the [Features](#features) below for details of each command. -------------------------------------------------------------------------------------------------------------------- +## Parameters + +The list below contains the parameters that are used in various commands as well as their various constraints. + +| Parameter | Constraints | Valid Examples | Invalid Examples | +|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------|:-------------------------| +| NRIC | Starting with S or T, followed by 7 numbers, and ends with a letter. Not case-sensitive. | T0123456G, s33344476i | T01234567G, “”, t12367K | +| Doctor/Patient name | Empty strings are not allowed. Name must contain only alphanumeric characters. | Cristiano Ronaldo, Tanveer Singh | “”, 高橋紳助, s/o someone | +| Contact number | 3 digit or more integer as phone number. Empty strings are not allowed. | 91234569 | “”, 99 | +| Email | Must be of the format `local-name`@`domain`. `local-name` should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-), and may not start or end with any special characters. `domain` is made up of domain labels separated by periods, and must end with a domain label at least 2 characters long. Domain labels start and end with alphanumeric characters, consist of alphanumeric characters, separated only by hyphens, if any. | j@Email.com, isaac@a-b.com | isaac@a+b.com, james.com | +| Blood type | Accepts only strings containing valid blood types, that is a combination of A/B/AB/O and +/-. | B+, O, B-, AB | J, K | +| Address | Any non-empty string. | Clementi, OneCare@Hougang Avenue | "" | +| Gender | Either the character “M” or “F”. | M, F | G, girl, male | +| Emergency Contact | Valid Contact number. Same constraints as the Contact Number parameter. | 91234569 | “”, 99 | +| Condition | Any non-empty string. | Knee Injury, appendicitis | "" | +| Patient Tag | Accepts only strings containing valid priority levels, either low, medium or high. Not case-sensitive | low, MEDIUM, hiGh | extreme, med | +| Doctor Tag | Accepts only strings containing valid specialisations. Not case-sensitive. The current allowed specialisations are listed in the examples box. | CARDIOLOGIST, ORTHOPEDIC, PEDIATRICIAN, DERMATOLOGIST, NEUROLOGIST, GENERAL_PRACTITIONER, PSYCHIATRIST, SURGEON | Nurse, Head-Doctor | ## Features @@ -75,7 +93,7 @@ can get your patients management tasks done faster than traditional GUI apps. ### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page. ![help message](images/helpMessage.png) @@ -83,13 +101,13 @@ Format: `help` ### Adding a Doctor: `add-doctor` -Adds a Doctor to the hospital database. +Adds a Doctor to the clinic database. Format: `add-doctor n/NAME ic/IC g/GENDER p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
:bulb: **Tip:** -A person can have any number of tags (including 0) +A doctor can have any number of tags (including 0). Duplicate tags, however, are NOT allowed.
@@ -97,23 +115,29 @@ A person can have any number of tags (including 0) - A doctor **MUST** have a non-empty NAME and a valid IC at the very least. Failure to include these details may result in an error. +- A person can either be a doctor or a patient, but not both. Hence if the doctor's IC is already in the app +as a patient, it may result in an error. - Phone Numbers and Emails have to be in a valid format. - PHONE_NUMBER must have exactly 8 digits. - EMAIL must contain email domain (eg. `@gmail.com`). - PATIENT must contain the valid IC of a Patient in the Database. +- Tags for doctors represent the specialisation(s) of the doctor. Only tags from the list below are supported +in our current version: + `CARDIOLOGIST, ORTHOPEDIC, PEDIATRICIAN, DERMATOLOGIST, NEUROLOGIST, GENERAL_PRACTITIONER, PSYCHIATRIST, SURGEON` +- Tags are not case-sensitive (e.g. `t/SURGEON` and `t/surgeon` are both valid inputs).
Examples: -* `add-doctor n/John Doe ic/S9851386G g/M p/98765432 e/johnd@example.com a/John street, block 123, #01-01 pt/T0123456H` +* `add-doctor n/John Doe ic/S9851386G g/M p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/Pediatrician` * `add-doctor n/Betsy Crowe ic/S9851586G g/F p/98765433 e/betsycrowe@example.com a/#104-C, Wakanda St 42 t/Surgeon` ### Adding a Patient: `add-patient` -Adds a Patient to the hospital database. +Adds a Patient to the clinic database. -Format: `add-patient n/NAME ic/IC g/GENDER p/PHONE_NUMBER ec/EMERGENCY_CONTACT e/EMAIL a/ADDRESS [t/TAG] [d/DOCTOR] [c/CONDITION] [b/BLOODTYPE] …​` +Format: `add-patient n/NAME ic/IC g/GENDER p/PHONE_NUMBER ec/EMERGENCY_CONTACT e/EMAIL a/ADDRESS c/CONDITION b/BLOODTYPE [t/TAG] ​`
@@ -121,43 +145,90 @@ Format: `add-patient n/NAME ic/IC g/GENDER p/PHONE_NUMBER ec/EMERGENCY_CONTACT e - A patient **MUST** have a non-empty NAME and a valid IC at the very least. Failure to include these details may result in an error. +- A person can either be a doctor or a patient, but not both. Hence, if the patient's IC is already in the app + as a doctor, it may result in an error. - Phone Numbers and Emails have to be in a valid format. - - PHONE_NUMBER must have exactly 8 digits. + - PHONE_NUMBER must have at least 3 digits. - EMAIL must contain email domain (eg. `@gmail.com`). -- DOCTOR must contain the valid IC of a doctor in the Database. -- EMERGENCY_CONTACT must contain valid emergency contact number, which needs to be a valid phone number. -- Blood type must be a combination of A/B/AB/O and +/- +- TAG must indicate Priority Level of the Patient and be one of the following: + - Low + - Medium + - High +- EMERGENCY_CONTACT must contain valid emergency contact number, which needs to be a valid phone number. This number can be the same the person's contact number. +- Blood type must be a combination of A/B/AB/O and +/-. +- A patient can only have up to one tag at any time. +- Tags for patients represent the priority level of the patient. Only the following tags are allowed: Low, Medium, High. +- Tags are not case-sensitive (e.g. `t/LOW` and `t/low` are both valid inputs).
Examples: -* `add-patient n/John Doe ic/S9851386G g/M p/98765432 ec/90123456 e/johnd@example.com a/John street, block 123, #01-01 d/T0123456H c/pneumothorax b/O+` -* `add-patient n/Betsy Crowe ic/S9851586G g/F p/98765433 e/betsycrowe@example.com a/#104-C, Wakanda St 42 t/High Priority pt/T0123556H` +* `add-patient n/John Doe ic/S9851386G g/M p/98765432 ec/90123456 e/johnd@example.com a/John street, block 123, #01-01 c/pneumothorax b/O+ t/Low` +* `add-patient n/Betsy Crowe ic/S9851586G g/F p/98765433 ec/12345678 e/betsycrowe@example.com a/#104-C, Wakanda St 42 c/AIDS b/O+ t/High` ### Creating an Appointment : `new-appt` Creates a new appointment for patients. -Format: `new-appt pic/IC dic/IC time/yyyy-MM-dd HH:mm:ss` +Format: `new-appt pic/IC dic/IC time/yyyy-MM-dd HH:mm`
**:information_source: Take Note:**
- All fields are Required. -- EMAIL must follow the specified format (ie. `yyyy-MM-dd HH:mm:ss`). +- TIME must follow the specified format (ie. `yyyy-MM-dd HH:mm`), where `HH:mm` follows the 24hr format. - PATIENT must contain the valid IC of a Patient in the Database. - DOCTOR must contain the valid IC of a Doctor in the Database. +- There must not be conflicting Appointments. (eg the doctor already has an appointment with another patient at the same time) However, the duration of each appointment is flexible and up to the users. As long as appointments are not at the exact same time, users can add it in.
Examples: -* `new-appt pic/T0123456H dic/S9851586G time/2023-10-30T13:00:00` +* `new-appt pic/T0123456H dic/S9851586G time/2023-10-30 13:00` + +### Deleting an Appointment : `delete-appt` + +Deletes an existing appointment. + +Format: `delete-appt INDEX` + +
+**:information_source: Take Note:**
+ +- Index is according to the list view in the application. +- Use `list` command to find index of desired appointment. + +
+ +Examples: + +* `delete-appt 1` + +### Finding a Appointment : `find-appt` + +Finds all appointments that involve a specific patient/doctor. + +Format: `find-appt NRIC` + +
+**:information_source: Take Note:**
+ +- All fields are Required. +- NRIC must contain the valid NRIC of a Patient or Doctor in the Database. +- Either Doctor NRIC or Patient NRIC can be used in the search +- It is recommended to use `list` to restore the view of all data after a `find` command. + +
+ +Examples: + +* `find-appt T0001222Q` ### Listing all persons : `list` -Shows a list of all persons in the MediLink Contacts. +Shows a list of all persons and appointments in the MediLink Contacts. Format: `list` @@ -169,44 +240,83 @@ Format: `edit NRIC [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` * Edits the person at the specified `NRIC`. The NRIC **must be a valid IC number** * At least one of the optional fields must be provided. +* If the provided fields are the same as the original, the command will still work. * Must edit appropriate fields based on whether the person is a patient or doctor (e.g. can't update condition of a doctor) * 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. +* Note: In our app, the Remark Section will be left blank by default. Edit Command can be used to add any misc info not captured by other fields such as possible allergies, medical history, etc. Examples: * `edit T0123456A p/91234567 e/johndoe@example.com g/F` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit S9876543B pt/T0123456A n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all +* `edit S9876543B n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. ### Locating persons by name: `find` -Finds persons that match the query. Supports gender, NRIC and name. +Finds persons that match the query. Format: `find KEYWORD [MORE_KEYWORDS]` -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). +* When searching names, the search is case-insensitive. e.g `hans` will match `Hans` +* When searching names, the order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` +* When searching names, only full words will be matched e.g. `Han` will not match `Hans` +* When searching names, Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` -* When searching by NRIC, input the NRIC as the keyword. -* When searching by gender, input either `M` or `F` as the keyword. -* When searching by name, input the names as per above. +* Note that if the name coincides with other find commands, it will be interpreted as the other find command first and extraneous paremeters will be ignored. e.g. `find F Kennedy John` will search for all female persons. +* It is recommended to use `list` to restore the view of all data after a `find` command Examples: * `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -* `find T1125726G` returns the person with the matching NRIC. +* `find kenny pickens` returns `Kenny Pickett`, `George Pickens`
+ ![result for 'find alex david'](images/findpickettpickensresult.png) + + +### Locating a person by NRIC : `find` ### + +Finds person that matches the NRIC query + +Format: `find NRIC` + +* NRIC input must be capitalised! +* It is recommended to use `list` to restore the view of all data after a `find` command + +Examples: + +* `find T1125726G` returns the person with the matching NRIC + +### Locating people by gender : `find M`, `find F` ### + +Finds all persons with matching gender + +Format: `find M` or `find F` + +* M and F must be capitalised +* It is recommended to use `list` to restore the view of all data after a `find` command + +Examples: + * `find M` returns all male persons. +### Locating people by blood types : `find Blood Type` ### + +Finds all Patients with query blood type + +Format: `find Blood Type QUERY` + +* All blood type inputs must be capitalised +* Acceptable blood types are A, A+, B, B+, O, O+, AB and AB+ +* It is recommended to use `list` to restore the view of all data after a `find` command + +Examples: + +* `find Blood Type A+` returns all Patients with blood type A+ + ### Deleting a person : `delete` Deletes the specified person from the address book. @@ -233,7 +343,7 @@ Undoes the effect of the last command. Format: `undo` -* Can only do upto 5 undos at any one time. +* Can only do up to 5 undos at any one time. ### Redo last action : `redo` @@ -241,7 +351,7 @@ Repeats the previous command; an `undo` for an `undo` command. Format: `redo` -* Can only do upto 5 redos at any one time. +* Can only do up to 5 redos at any one time. ### Exiting the program : `exit` @@ -256,12 +366,11 @@ need to save manually. ### Editing the data file -MediLink Contacts data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced -users are welcome to update data directly by editing that data file. +MediLink Contacts data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. However, please ensure that the edits are valid, else it may cause unexpected behaviours when the invalid data is not detected by the system.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, MediLink Contacts will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +If your changes to the data file makes its format invalid, MediLink Contacts may discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. Some changes may also be invalid, but not detected by the system. In that case, there may be many unexpected behaviours due to those undetected errors.
### Archiving data files `[coming in v2.0]` @@ -288,17 +397,20 @@ the data of your previous MediLink Contacts home folder. ## Command summary -| Action | Format, Examples | -|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **New Doctor** | `add-doctor n/NAME ic/IC g/GENDER p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add-doctor n/John Doe ic/S9851386G g/M p/98765432 e/johnd@example.com a/John street, block 123, #01-01 pt/T0123456H` | -| **New Patient** | `add-patient n/NAME ic/IC g/GENDER p/PHONE_NUMBER ec/EMERGENCY_CONTACT e/EMAIL a/ADDRESS [t/TAG] [d/DOCTOR] [c/CONDITION] [b/BLOODTYPE] …​`
e.g., `add-patient n/John Doe ic/S9851386G g/M p/98765432 ec/90123456 e/johnd@example.com a/John street, block 123, #01-01 d/T0123456H c/pneumothorax b/O+` | -| **New Appointment** | `new-appt pic/IC dic/IC time/yyyy-MM-dd HH:mm:ss`
e.g., `new-appt pic/T0123456H dic/S9851586G time/yyyy-MM-dd 13:00:00` | -| **Clear** | `clear` | -| **Undo** | `undo` | -| **Redo** | `redo` | -| **Delete** | `delete INDEX`
e.g., `delete 3` | -| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` | -| **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | -| **List** | `list` | -| **Help** | `help` | +| Action | Format, Examples | +|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **New Doctor** | `add-doctor n/NAME ic/IC g/GENDER p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add-doctor n/John Doe ic/S9851386G g/M p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/Pediatrician` | +| **New Patient** | `add-patient n/NAME ic/IC g/GENDER p/PHONE_NUMBER ec/EMERGENCY_CONTACT e/EMAIL a/ADDRESS [t/TAG] [d/DOCTOR] [c/CONDITION] [b/BLOODTYPE] …​`
e.g., `add-patient n/Betsy Crowe ic/S9851586G g/F p/98765433 e/betsycrowe@example.com a/#104-C, Wakanda St 42 c/AIDS b/O+ t/High` | +| **New Appointment** | `new-appt pic/IC dic/IC time/yyyy-MM-dd HH:mm`
e.g., `new-appt pic/T0123456H dic/S9851586G time/2023-10-30 13:00` | +| **Delete Appointment** | `delete-appt INDEX`
e.g., delete-appt 1 | +| **Find Appointment** | `find-appt NRIC`
e.g., find-appt T00012220 | +| **Clear** | `clear` | +| **Undo** | `undo` | +| **Redo** | `redo` | +| **Delete** | `delete NRIC`
e.g., `delete T0666485G` | +| **Edit** | `edit NRIC [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit S9760431H n/James Lee e/jameslee@example.com` | +| **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | +| **List** | `list` | +| **Help** | `help` | +| **Exit** | `exit` | diff --git a/docs/_config.yml b/docs/_config.yml index 2d7e33d4083..2b453166440 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "https://github.com/AY2324S1-CS2103T-T09-3/tp" +repository: "AY2324S1-CS2103T-T09-3/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 40ea6c9dc4c..6e80f62c935 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -14,10 +14,10 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete NRIC") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete NRIC") activate AddressBookParser create DeleteCommandParser @@ -27,7 +27,7 @@ activate DeleteCommandParser DeleteCommandParser --> AddressBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +AddressBookParser -> DeleteCommandParser : parse("NRIC") activate DeleteCommandParser create DeleteCommand @@ -49,7 +49,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deletePerson(NRIC) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/tracing/DeleteSequenceDiagram.png b/docs/diagrams/tracing/DeleteSequenceDiagram.png new file mode 100644 index 00000000000..580f7e1c222 Binary files /dev/null and b/docs/diagrams/tracing/DeleteSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index e186f7ba096..580f7e1c222 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..c60e490a992 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/findpickettpickensresult.png b/docs/images/findpickettpickensresult.png new file mode 100644 index 00000000000..2684da25f54 Binary files /dev/null and b/docs/images/findpickettpickensresult.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..0be846d7728 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/team/chonguschonguschongus.md b/docs/team/chonguschonguschongus.md index 9bd998f41b3..1e8a10ab3ed 100644 --- a/docs/team/chonguschonguschongus.md +++ b/docs/team/chonguschonguschongus.md @@ -3,19 +3,22 @@ layout: page title: Isaac's Project Portfolio Page --- -### Project: AddressBook Level 3 +### Project: MediLink Contacts -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +MediLink Contacts aims to help medical staff including nurses/doctors/pharmacists navigate through patient details in +their high workload and time-pressured working environment. When medical emergencies arise, it becomes crucial to +provide rapid access to emergency contacts for patients and access other details of the patients to make decisions more +quickly. It is optimised for CLI so that users can quickly access the information. There is also a GUI created with +JavaFX. Given below are my contributions to the project. -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. +* **New Feature**: Enhanced the Find feature to function with NRIC, blood type, gender as well as name. + * What it does: Allows users to search for Patients/Doctors by their NRIC, blood type, gender or simply name + * Justification: Users can search by a specific attribute, increasing convenience + +* **New Feature**: Added a Find Appointment function to locate a specific appointment by NRIC of people involved +* **New Feature** Added a Delete Appointment function to delete a specific appointment by NRIC of people involved * **Code contributed**: [RepoSense link]() @@ -23,13 +26,13 @@ Given below are my contributions to the project. * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub * **Enhancements to existing features**: + * Wrote an abstract UniqueObjectList that can be inherited to form UniqueLists of Patients, Doctors and Appointments * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + * Wrote additional tests for existing features (Pull requests [\#36](), [\#38]()) * **Documentation**: * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Added documentation for the features `find`, `delete-appt`, `find-appt` [\#72]() * Developer Guide: * Added implementation details of the `delete` feature. diff --git a/docs/team/cmhuang777.md b/docs/team/cmhuang777.md index d578b766749..3967e75248c 100644 --- a/docs/team/cmhuang777.md +++ b/docs/team/cmhuang777.md @@ -5,7 +5,7 @@ title: Chao Ming's Project Portfolio Page ### Project: MediLink Contacts -MediLink Contacts aims to help medical staff including nurses/doctors/pharmacists navigate through patient details in +MediLink Contacts aims to help medical staff including nurses/doctors navigate through patient details in their high workload and time-pressured working environment. When medical emergencies arise, it becomes crucial to provide rapid access to emergency contacts for patients and access other details of the patients to make decisions more quickly. It is optimised for CLI so that users can quickly access the information. There is also a GUI created with @@ -17,19 +17,29 @@ Given below are my contributions to the project. [Link to TP code dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=cmhuang777&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos&tabOpen=true&tabType=authorship&tabAuthor=cmHuang777&tabRepo=AY2324S1-CS2103T-T09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) * **New Feature**: Created new Patient class + * Adapted UI and Storage to store Patients instead of Person + * Changed Person card to patient card so that it can get displayed on the UI -* **New Feature**: Modified current add person command to add new Patient class. +* **New Feature**: Modified current add person command to add new Patient class +* **New Feature**: Added new command and parser to add new appointment for patients and doctors + * Also updated the storage for the appointments +* **New Feature**: Created a new Class AppointmentTime to represent the time object used in Appointment +* **New Feature**: Updated part of the sample data to include the new features * **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + * Managed releases `v1.3 trial` and `v1.3` on GitHub * Managed milestones and issues * **Enhancements to existing features**: * (to be added soon) * **Documentation**: - * User Guide: (to be added soon) - * Developer Guide: (to be added soon) + * User Guide: + * Updated existing content to our project + * Added the section on adding new patients + * Developer Guide: + * Included the section for add patient/doctor feature and added a new sequence diagram for that feature + * Added use case for adding new patient * **Team-based tasks**: * Forked the team repo from the source code diff --git a/docs/team/kohkaijie.md b/docs/team/kohkaijie.md index 4c242ce1785..c0b327899b4 100644 --- a/docs/team/kohkaijie.md +++ b/docs/team/kohkaijie.md @@ -3,44 +3,45 @@ layout: page title: Kai Jie's Project Portfolio Page --- -### Project: AddressBook Level 3 +### Project: MediLink Contacts -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +MediLink Contacts aims to help medical staff including nurses/doctors/pharmacists navigate through patient details in +their high workload and time-pressured working environment. When medical emergencies arise, it becomes crucial to +provide rapid access to emergency contacts for patients and access other details of the patients to make decisions more +quickly. It is optimised for CLI so that users can quickly access the information. There is also a GUI created with +JavaFX. Given below are my contributions to the project. -* **New Feature**: Added the ability to undo/redo previous commands. +* **New Feature**: Added the ability to undo/redo previous commands (done with Tanveer). * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. +* **New Feature**: Allowed only specific tags to be added for patients and doctors respectively. + * What it does: restricts the user to only enter specific tags for patients and doctors. Patients can only have up to one tag specifying their priority level. Doctors can have up to multiple tags specifying their specialisations. + * Justification: This feature ensures that tags have a clear purpose and users cannot enter tags that do not add value to the patient or doctor (e.g adding Engineer tag for a doctor). + * Highlights: While this enhancement itself did not affect much other commands, the implementation itself was challenging as it required choosing between different implementation methods, and choosing the most future-proof and OOP-friendly one. -* **Code contributed**: [RepoSense link]() +* **Code contributed**: [Link to TP code dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=kohkaijie&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) * **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + * Managed releases `v1.1` - `v1.3` (3 releases) on GitHub + * Added issues on GitHub for various tasks. * **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + * Created classes for new fields for Patient and Doctor classes. + * Wrote additional tests for existing features such as Patient fields, UndoCommand and Tags. * **Documentation**: * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Added documentation for the features `delete` and usage of special tags. * Developer Guide: * Added implementation details of the `delete` feature. + * Modified UML diagram for DeleteSequenceDiagram. + * Added Use Case for UndoCommand and extensions for EditCommand. -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) +* **Team-based tasks**: + * Reviewed PRs and approved when needed for merging. -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/mohammed-faizzzz.md b/docs/team/mohammed-faizzzz.md index ca486714437..3f45243817d 100644 --- a/docs/team/mohammed-faizzzz.md +++ b/docs/team/mohammed-faizzzz.md @@ -3,44 +3,41 @@ layout: page title: Faiz's Project Portfolio Page --- -### Project: AddressBook Level 3 +### Project: MediLink Contacts -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +MediLink Contacts aims to help medical staff including nurses/doctors navigate through patient details in +their high workload and time-pressured working environment. When medical emergencies arise, it becomes crucial to +provide rapid access to emergency contacts for patients and access other details of the patients to make decisions more +quickly. It is optimised for CLI so that users can quickly access the information. There is also a GUI created with +JavaFX. Given below are my contributions to the project. -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* +* **Code contributed**: + [Link to TP code dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=Faiz&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22) -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() +* **New Feature**: Created new Doctor class + * Adapted UI and Storage to store Doctors instead of Person + * Changed Person card to Doctor card so that it can get displayed on the UI + * Modified UI to also display Doctors as a List alongside the Patients +* **New Feature**: Modified current add person command to add new Doctor class +* **New Feature**: Created a new Class Appointment to represent an Appointment between a pre-existing Doctor and Patient + * Added `new-appt` command to add new Appointment +* **New Feature**: Updated part of the sample data to include the new features * **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + * Managed milestones and issues + * Did Peer Reviews on other teammates' Pull Requests + * Resolved Merge Conflicts where applicable * **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + * (to be added soon) * **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ + * User Guide: + * Updated existing content to our project + * Added the section on adding new doctors + * Added the section on adding new appointments + * Proofread other sections and added the commands/sample commands to a summary table. + + diff --git a/docs/team/tanveersingh10.md b/docs/team/tanveersingh10.md index 43605ed4335..28a8651f1ae 100644 --- a/docs/team/tanveersingh10.md +++ b/docs/team/tanveersingh10.md @@ -3,44 +3,40 @@ layout: page title: Tanveer's Project Portfolio Page --- -### Project: AddressBook Level 3 +### Project: MediLink Contacts -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +MediLink Contacts aims to help medical staff including nurses/doctors navigate through patient details in +their high workload and time-pressured working environment. When medical emergencies arise, it becomes crucial to +provide rapid access to emergency contacts for patients and access other details of the patients to make decisions more +quickly. It is optimised for CLI so that users can quickly access the information. There is also a GUI created with +JavaFX. Given below are my contributions to the project. +* **Code contributed**: + [Link to TP code dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=tanveersingh10&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos&tabOpen=true&tabType=authorship&tabAuthor=tanveersingh10&tabRepo=AY2324S1-CS2103T-T09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + * **New Feature**: Added the ability to undo/redo previous commands. * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. -* **Code contributed**: [RepoSense link]() +* **New Feature**: Implemented the Edit Command + * What it does: allows to update fields of a doctor or patient. + * Justification: This feature improves the product significantly because a user can make mistakes and may need to change details of a person. Alternatively, it's possible a + person's details change over time, such as their condition or remarks, and there needs to be a quick way to update the information. * **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + * Led the ideation process. Had to find a balance between new features and ensuring current features are bug free. + * Helped delegate issues and tickets + * Did Peer Reviews on other teammates' Pull Requests + * Resolved Merge Conflicts where applicable * **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + * Wrote additional tests for existing features to increase test coverage. * **Documentation**: * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Added documentation for the features `edit`, `undo` and `redo` * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ + * Added implementation details of the `edit` feature. diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index e68fcc90428..e461661e614 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,6 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; import seedu.address.model.person.Patient; @@ -40,6 +41,7 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of doctors */ ObservableList getFilteredDoctorList(); + ObservableList getFilteredAppointmentList(); /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index fe0f2e69e08..8c98ffd3a5e 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,5 +1,7 @@ package seedu.address.logic; +import static java.util.Objects.requireNonNull; + import java.io.IOException; import java.nio.file.AccessDeniedException; import java.nio.file.Path; @@ -15,6 +17,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; import seedu.address.model.person.Patient; import seedu.address.storage.Storage; @@ -38,6 +41,8 @@ public class LogicManager implements Logic { * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. */ public LogicManager(Model model, Storage storage) { + requireNonNull(model); + requireNonNull(storage); this.model = model; this.storage = storage; addressBookParser = new AddressBookParser(); @@ -45,6 +50,7 @@ public LogicManager(Model model, Storage storage) { @Override public CommandResult execute(String commandText) throws CommandException, ParseException { + requireNonNull(commandText); logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; @@ -76,6 +82,11 @@ public ObservableList getFilteredDoctorList() { return model.getFilteredDoctorList(); } + @Override + public ObservableList getFilteredAppointmentList() { + return model.getFilteredAppointmentList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index 271c7caed66..2a2c08675c7 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,6 +5,7 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; /** @@ -19,6 +20,8 @@ public class Messages { public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_APPOINTMENT_NOT_FOUND = "Specified appointment does not exist!"; + public static final String MESSAGE_APPOINTMENTS_FOUND_OVERVIEW = "%1d appointments found!"; /** * Returns an error message indicating the duplicate prefixes. @@ -51,4 +54,18 @@ public static String format(Person person) { return builder.toString(); } + /** + * Formats the {@code appointment} for display to the user. + */ + public static String format(Appointment appointment) { + final StringBuilder builder = new StringBuilder(); + builder.append("Patient involved: ") + .append(appointment.getPatient()) + .append("; Doctor involved: ") + .append(appointment.getDoctor()) + .append("; Time of appointment: ") + .append(appointment.getAppointmentTime()); + return builder.toString(); + } + } diff --git a/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java index 0d536e27b07..a45f8febe58 100644 --- a/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java @@ -16,7 +16,7 @@ import seedu.address.model.person.Patient; /** - * Adds a person to the address book. + * Adds an Appointment to the address book. */ public class AddAppointmentCommand extends Command { @@ -56,34 +56,37 @@ public AddAppointmentCommand(Appointment appointment) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - Patient chosenPatient = findPatient(model); - Doctor chosenDoctor = findDoctor(model); - if (chosenPatient == null) { - throw new CommandException(MESSAGE_INVALID_PATIENT); - } - if (chosenDoctor == null) { - throw new CommandException(MESSAGE_INVALID_DOCTOR); - } - - // check that patient and doctor are not the same person - if (chosenPatient.isSamePerson(chosenDoctor)) { + if (toAdd.getDoctor().equals(toAdd.getPatient())) { throw new CommandException(MESSAGE_SAME_DOCTOR_AND_PATIENT); } + Patient chosenPatient = findPatient(model); + Doctor chosenDoctor = findDoctor(model); + checkPatientAndDoctor(chosenPatient, chosenDoctor); + checkValidAppointment(chosenPatient, chosenDoctor, toAdd); + chosenPatient.addAppointment(toAdd); + chosenDoctor.addAppointment(toAdd); + model.addAppointment(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } - // check that the patient and doctor do not have appointment scheduled at the same time + private void checkValidAppointment(Patient chosenPatient, Doctor chosenDoctor, Appointment toAdd) + throws CommandException { if (chosenPatient.hasAppointmentAt(toAdd.getAppointmentTime())) { throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT_PATIENT); } if (chosenDoctor.hasAppointmentAt(toAdd.getAppointmentTime())) { throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT_DOCTOR); } - // add appointment to the specified doctor's appointment set - // add appointment to the specified patient's appointment set - chosenPatient.addAppointment(toAdd); - chosenDoctor.addAppointment(toAdd); + } - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + private void checkPatientAndDoctor(Patient chosenPatient, Doctor chosenDoctor) throws CommandException { + if (chosenPatient == null) { + throw new CommandException(MESSAGE_INVALID_PATIENT); + } + if (chosenDoctor == null) { + throw new CommandException(MESSAGE_INVALID_DOCTOR); + } } private Patient findPatient(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/AddDoctorCommand.java b/src/main/java/seedu/address/logic/commands/AddDoctorCommand.java index 2a7c951d7eb..75207d50ce5 100644 --- a/src/main/java/seedu/address/logic/commands/AddDoctorCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddDoctorCommand.java @@ -9,6 +9,9 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; @@ -37,12 +40,12 @@ public class AddDoctorCommand extends Command { + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + PREFIX_GENDER + "M " - + PREFIX_NRIC + "S1234567Z" - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_NRIC + "S1234567Z " + + PREFIX_TAG + "SURGEON"; public static final String MESSAGE_SUCCESS = "New doctor added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + private static final Logger logger = LogsCenter.getLogger(AddDoctorCommand.class); private final Doctor toAdd; /** @@ -57,14 +60,15 @@ public AddDoctorCommand(Doctor doctor) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { + if (model.hasIc(toAdd.getIc()) || model.hasPerson(toAdd)) { + logger.warning("Can't add doctor as doctor already exists"); throw new CommandException(MESSAGE_DUPLICATE_PERSON); } model.addPerson(toAdd); + logger.info("Successfully added doctor"); return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } - @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/logic/commands/AddPatientCommand.java b/src/main/java/seedu/address/logic/commands/AddPatientCommand.java index 7aadb79c381..9ff8d5838ce 100644 --- a/src/main/java/seedu/address/logic/commands/AddPatientCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddPatientCommand.java @@ -2,6 +2,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT; import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; @@ -32,16 +34,20 @@ public class AddPatientCommand extends Command { + PREFIX_ADDRESS + "ADDRESS " + PREFIX_GENDER + "GENDER " + PREFIX_NRIC + "NRIC " + + PREFIX_CONDITION + "CONDITION" + + PREFIX_BLOODTYPE + "BLOOD TYPE" + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " + + PREFIX_NAME + "Kenny Pickett " + PREFIX_PHONE + "98765432 " + + PREFIX_EMERGENCY_CONTACT + "88884444 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + PREFIX_GENDER + "M " + PREFIX_NRIC + "S1234567Z " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_CONDITION + "COVID " + + PREFIX_BLOODTYPE + "A+ " + + PREFIX_TAG + "MEDIUM"; public static final String MESSAGE_SUCCESS = "New patient added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; @@ -60,7 +66,7 @@ public AddPatientCommand(Patient patient) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { + if (model.hasIc(toAdd.getIc()) || model.hasPerson(toAdd)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } 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 00000000000..cda2331924a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java @@ -0,0 +1,108 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.person.Doctor; +import seedu.address.model.person.Ic; +import seedu.address.model.person.Patient; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "delete-appt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the person identified by specified index in the Appointments list.\n" + + "Parameters: valid integer as shown in Appointments list \n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_APPOINTMENT_SUCCESS = "Deleted Appointment: %1$s"; + private static final Logger logger = LogsCenter.getLogger(DeleteAppointmentCommand.class); + + private final int targetIndex; + private Appointment toDelete; + + public DeleteAppointmentCommand(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = new ArrayList<>(); + lastShownList.addAll(model.getFilteredAppointmentList()); + try { + this.toDelete = lastShownList.get(targetIndex - 1); + Patient targetPatient = findPatient(model, toDelete); + Doctor targetDoctor = findDoctor(model, toDelete); + targetPatient.deleteAppointment(toDelete); + targetDoctor.deleteAppointment(toDelete); + model.deleteAppointment(toDelete); + List updatedList = new ArrayList<>(); + updatedList.addAll(model.getFilteredAppointmentList()); + assert updatedList.size() < lastShownList.size(); + logger.info("Successfully deleted appointment"); + return new CommandResult(String.format(MESSAGE_DELETE_APPOINTMENT_SUCCESS, Messages.format(toDelete))); + } catch (IndexOutOfBoundsException e) { + logger.warning("Appointment does not exist"); + throw new CommandException(Messages.MESSAGE_APPOINTMENT_NOT_FOUND); + } + } + + private Patient findPatient(Model model, Appointment toDelete) { + Ic patientIc = toDelete.getPatient(); + List patients = model.getFilteredPatientList(); + for (Patient p : patients) { + if (p.hasIc(patientIc)) { + return p; + } + } + return null; + } + + private Doctor findDoctor(Model model, Appointment toDelete) { + Ic doctorIc = toDelete.getDoctor(); + List doctors = model.getFilteredDoctorList(); + for (Doctor d : doctors) { + if (d.hasIc(doctorIc)) { + return d; + } + } + return null; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteAppointmentCommand)) { + return false; + } + + DeleteAppointmentCommand otherDeleteAppointmentCommand = (DeleteAppointmentCommand) other; + return targetIndex == otherDeleteAppointmentCommand.targetIndex; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIc", targetIndex) + .toString(); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index aa0e1583921..8272ea87b03 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -4,7 +4,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; @@ -25,6 +27,7 @@ public class DeleteCommand extends Command { + "Example: " + COMMAND_WORD + " T0333789H"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + private static final Logger logger = LogsCenter.getLogger(DeleteCommand.class); private final Ic targetIc; @@ -43,9 +46,15 @@ public CommandResult execute(Model model) throws CommandException { for (Person target : lastShownList) { if (targetIc.equals(target.getIc())) { model.deletePerson(target); + List updatedList = new ArrayList<>(); + updatedList.addAll(model.getFilteredDoctorList()); + updatedList.addAll(model.getFilteredPatientList()); + assert updatedList.size() < lastShownList.size(); + logger.info("Successfully deleted person"); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(target))); } } + logger.warning("Could not find person to delete"); throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index ad5a2cbe4fb..e686c196983 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -8,7 +8,6 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMERGENCY_CONTACT; import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -21,8 +20,10 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.CollectionUtil; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -47,7 +48,6 @@ * Edits the details of an existing person in the address book. */ public class EditCommand extends Command { - public static final String COMMAND_WORD = "edit"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " @@ -61,22 +61,20 @@ public class EditCommand extends Command { + "[" + PREFIX_GENDER + "GENDER] " + "[" + PREFIX_CONDITION + "CONDITION] " + "[" + PREFIX_BLOODTYPE + "BLOOD TYPE] " - + "[" + PREFIX_NRIC + "NRIC] " + "[" + PREFIX_EMERGENCY_CONTACT + "EMAIL] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " T0123456H " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com" + PREFIX_REMARK + "remarks"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; public static final String MESSAGE_DOESNT_EXIST = "This person hasn't been saved"; - + public static final String MESSAGE_IC_CHANGED = "You can't change a person's IC"; + private static final Logger logger = LogsCenter.getLogger(EditCommand.class.getName()); private final Ic nric; private final EditPersonDescriptor editPersonDescriptor; - private String personRole; /** * @param nric of the person in the filtered person list to edit @@ -93,52 +91,73 @@ public EditCommand(Ic nric, EditPersonDescriptor editPersonDescriptor) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + if (editPersonDescriptor.getIc().isPresent() && !(editPersonDescriptor.getIc().get().equals(nric))) { + throw new CommandException(MESSAGE_IC_CHANGED); + } + // combine doctor list and patient list + Person personToEdit = getPersonToEdit(model); + Person editedPerson = getEditedPerson(model, personToEdit); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + logger.info("Successfully edited person"); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); + } + + private Person getPersonToEdit(Model model) throws CommandException { List lastShownList = new ArrayList<>(); lastShownList.addAll(model.getFilteredDoctorList()); lastShownList.addAll(model.getFilteredPatientList()); - List personToEditList = lastShownList.stream() .filter(x -> x.getIc().equals(nric)) .collect(Collectors.toList()); - if (personToEditList.size() == 0) { + logger.warning("Could not edit - person isn't in adressbook"); throw new CommandException(MESSAGE_DOESNT_EXIST); } - //developer assumption - can't have 2 people with same IC assert personToEditList.size() < 2; - Person personToEdit = personToEditList.get(0); - Person editedPerson; + return personToEdit; + } + private Person getEditedPerson(Model model, Person personToEdit) throws CommandException { + Person editedPerson; if (personToEdit instanceof Patient) { - personRole = "patient"; + if (editPersonDescriptor.getTags().isPresent() + && !editPersonDescriptor.isValidPatientTagList(editPersonDescriptor.getTags().get())) { + logger.warning("Invalid tag for patient"); + throw new CommandException("Please enter a valid patient tag."); + } editedPerson = createEditedPatient((Patient) personToEdit, editPersonDescriptor); } else { - assert personToEdit instanceof Doctor; + assert personToEdit.isDoctor(); if (editPersonDescriptor.getCondition().isPresent() || editPersonDescriptor.getBloodType().isPresent()) { + logger.warning("Error thrown - tried to edit condition / bloodtype of doctor"); throw new CommandException("Doctors cannot have Condition or BloodType fields."); } - personRole = "doctor"; - editedPerson = createEditedDoctor(personToEdit, editPersonDescriptor); + if (editPersonDescriptor.getTags().isPresent() + && !editPersonDescriptor.isValidDoctorTagList(editPersonDescriptor.getTags().get())) { + logger.warning(editPersonDescriptor.getTags().toString()); + throw new CommandException("Please enter valid Doctor tags."); + } + editedPerson = createEditedDoctor((Doctor) personToEdit, editPersonDescriptor); } - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + logger.warning("Edited Person and orignal person are the same"); throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); + return editedPerson; } /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. */ - - private static Doctor createEditedDoctor(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; + private static Doctor createEditedDoctor(Doctor personToEdit, EditPersonDescriptor editPersonDescriptor) { + requireNonNull(personToEdit); + requireNonNull(personToEdit); Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); @@ -146,18 +165,21 @@ private static Doctor createEditedDoctor(Person personToEdit, EditPersonDescript Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Remark updatedRemarks = editPersonDescriptor.getRemark().orElse(personToEdit.getRemark()); Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); - Ic updatedIc = editPersonDescriptor.getIc().orElse(personToEdit.getIc()); + Ic updatedIc = personToEdit.getIc(); // since you can't modify ic Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); Set updatedAppointments = editPersonDescriptor.getAppointments().orElse(personToEdit.getAppointments()); + logger.fine("Successfully created Edited Doctor"); return new Doctor(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemarks, updatedGender, updatedIc, updatedAppointments, updatedTags); } private static Patient createEditedPatient(Patient personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; + requireNonNull(personToEdit); + requireNonNull(personToEdit); + Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Phone updatedEmergencyContact = @@ -166,12 +188,13 @@ private static Patient createEditedPatient(Patient personToEdit, EditPersonDescr Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Remark updatedRemarks = editPersonDescriptor.getRemark().orElse(personToEdit.getRemark()); Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); - Ic updatedIc = editPersonDescriptor.getIc().orElse(personToEdit.getIc()); + Ic updatedIc = personToEdit.getIc(); // since you can't modify ic Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); Set updatedAppointments = editPersonDescriptor.getAppointments().orElse(personToEdit.getAppointments()); BloodType updatedBloodType = editPersonDescriptor.getBloodType().orElse(personToEdit.getBloodType()); Condition updatedCondition = editPersonDescriptor.getCondition().orElse(personToEdit.getCondition()); + logger.fine("Successfully created Edited Patient"); return new Patient(updatedName, updatedPhone, updatedEmergencyContact, updatedEmail, updatedAddress, updatedRemarks, updatedGender, updatedIc, updatedCondition, updatedBloodType, updatedAppointments, updatedTags); @@ -330,6 +353,38 @@ public Optional getBloodType() { return Optional.ofNullable(bloodType); } + /** + * Ensures the set of tags contains only valid patient tags. + * + * @param tagSet The set of tags to be validated. + * @return true if the tag set is valid (contains zero or one valid patient tag), false otherwise. + */ + public boolean isValidPatientTagList(Set tagSet) { + if (tagSet.size() > 1) { + return false; + } + for (Tag tag : tagSet) { + if (!tag.isValidPatientTag()) { + return false; + } + } + return true; + } + + /** + * Ensures the set of tags contains only valid doctor tags. + * + * @param tagSet The set of tags to be validated. + * @return true if the tag set is valid (contains no duplicate doctor tags), false otherwise. + */ + public boolean isValidDoctorTagList(Set tagSet) { + for (Tag tag : tagSet) { + if (!tag.isValidDoctorTag()) { + return false; + } + } + return true; + } /** * Sets {@code tags} to this object's {@code tags}. diff --git a/src/main/java/seedu/address/logic/commands/FindAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/FindAppointmentCommand.java new file mode 100644 index 00000000000..c48ccddb21b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindAppointmentCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; + +/** + * Finds and lists all persons in in address book whose attributes match the predicate. + * Keyword matching is case insensitive. + */ +public class FindAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "find-appt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all appointments who involve the " + + "the specified case-sensitive NRIC and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " T1234567Z"; + + private final Predicate predicate; + /** + * Finds and lists all persons in address book whose attributes match the predicate. + * Keyword matching is case-insensitive. + */ + public FindAppointmentCommand(Predicate predicate) { + requireNonNull(predicate); + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredAppointmentList(predicate); + List searchList = new ArrayList<>(); + searchList.addAll(model.getFilteredAppointmentList()); + return new CommandResult( + String.format(Messages.MESSAGE_APPOINTMENTS_FOUND_OVERVIEW, searchList.size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindAppointmentCommand)) { + return false; + } + + FindAppointmentCommand otherFindAppointmentCommand = (FindAppointmentCommand) other; + return predicate.equals(otherFindAppointmentCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 63a13709c64..63653f30cc7 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -12,7 +12,7 @@ import seedu.address.model.person.Person; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in in address book whose attributes match the predicate. * Keyword matching is case insensitive. */ public class FindCommand extends Command { @@ -25,8 +25,12 @@ public class FindCommand extends Command { + "Example: " + COMMAND_WORD + " alice bob charlie"; private final Predicate predicate; - + /** + * Finds and lists all persons in address book whose attributes match the predicate. + * Keyword matching is case insensitive. + */ public FindCommand(Predicate predicate) { + requireNonNull(predicate); this.predicate = predicate; } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..ea1d36fec2b 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_APPOINTMENTS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import seedu.address.model.Model; @@ -19,6 +20,7 @@ public class ListCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); 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 7faadf7430b..e29847db644 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -1,5 +1,10 @@ package seedu.address.logic.commands; +import static java.util.Objects.requireNonNull; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -12,10 +17,13 @@ public class RedoCommand extends Command { public static final String MESSAGE_SUCCESS = "Previous command redid successfully!"; public static final String MESSAGE_EMPTY = "There's nothing to redo"; + private static final Logger logger = LogsCenter.getLogger(RedoCommand.class); @Override public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); model.redo(); + logger.info("Successfully redid previously undone task"); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/RemarkCommand.java b/src/main/java/seedu/address/logic/commands/RemarkCommand.java deleted file mode 100644 index 89c5b2e1808..00000000000 --- a/src/main/java/seedu/address/logic/commands/RemarkCommand.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.ArrayList; -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; -import seedu.address.model.person.Remark; - -/** - * The RemarkCommand class represents a command in a software application for editing remarks associated with persons. - * This command allows the user to add or update a remark for a person identified by their index number in the last - * person listing. If a remark already exists for the person, it will be overwritten by the new input remark. - * If the remark input is empty, the existing remark for the person can be removed. - */ -public class RemarkCommand extends Command { - public static final String COMMAND_WORD = "remark"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " - + "Existing remark will be overwritten by the input.\n" - + "Parameters: INDEX (must be a positive integer) " - + "r/ [REMARK]\n" - + "Example: " + COMMAND_WORD + " 1 " - + "r/ Likes to swim."; - - public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s"; - public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s"; - - private final Index index; - private final Remark remark; - - /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to - */ - public RemarkCommand(Index index, Remark remark) { - requireAllNonNull(index, remark); - - this.index = index; - this.remark = remark; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - // combine doctor list and patient list - List lastShownList = new ArrayList<>(); - lastShownList.addAll(model.getFilteredDoctorList()); - lastShownList.addAll(model.getFilteredPatientList()); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = new Person( - personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), - personToEdit.getAddress(), remark, personToEdit.getGender(), - personToEdit.getIc(), personToEdit.getAppointments(), personToEdit.getTags()); - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); - - return new CommandResult(generateSuccessMessage(editedPerson)); - } - - /** - * Generates a command execution success message based on whether - * the remark is added to or removed from - * {@code personToEdit}. - */ - private String generateSuccessMessage(Person personToEdit) { - String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; - return String.format(message, personToEdit); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof RemarkCommand)) { - return false; - } - - RemarkCommand e = (RemarkCommand) other; - return index.equals(e.index) - && remark.equals(e.remark); - } -} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 9b96471bd04..f67ddb067c9 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -1,5 +1,10 @@ package seedu.address.logic.commands; +import static java.util.Objects.requireNonNull; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -9,13 +14,15 @@ */ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; - public static final String MESSAGE_SUCCESS = "Previous command undid successfully!"; public static final String MESSAGE_EMPTY = "There's nothing to undo"; + private static final Logger logger = LogsCenter.getLogger(UndoCommand.class); @Override public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); model.undo(); + logger.info("Successfully undid previous command"); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java index 16ce0987ebb..35a3279e3e6 100644 --- a/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java @@ -4,15 +4,13 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_DOCTOR_IC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PATIENT_IC; -import static seedu.address.model.appointment.Appointment.FORMATTER; -import java.time.LocalDateTime; -import java.time.format.DateTimeParseException; import java.util.stream.Stream; import seedu.address.logic.commands.AddAppointmentCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentTime; import seedu.address.model.person.Ic; /** @@ -26,6 +24,7 @@ public class AddAppointmentCommandParser implements Parser { - + private static final Logger logger = LogsCenter.getLogger(AddDoctorCommandParser.class); /** * Parses {@code userInput} into a command and returns it. * @@ -47,6 +49,7 @@ public AddDoctorCommand parse(String userInput) throws ParseException { if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_GENDER, PREFIX_NRIC) || !argMultimap.getPreamble().isEmpty()) { + logger.warning("Invalid command format for AddPatientCommand: " + userInput); throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddDoctorCommand.MESSAGE_USAGE)); } @@ -59,11 +62,13 @@ public AddDoctorCommand parse(String userInput) throws ParseException { Remark remark = new Remark(""); // add command does not allow adding remarks straight away Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); Ic ic = ParserUtil.parseIc(argMultimap.getValue(PREFIX_NRIC).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set tagList = ParserUtil.parseDoctorTags(argMultimap.getAllValues(PREFIX_TAG)); // appointments need to be added separately, so we initialise doctors with empty appointments Set appointmentList = new HashSet<>(); + Doctor doctor = new Doctor(name, phone, email, address, remark, gender, ic, appointmentList, tagList); + logger.info("Successfully parsed AddDoctorCommand with doctor: " + doctor); return new AddDoctorCommand(doctor); } diff --git a/src/main/java/seedu/address/logic/parser/AddPatientCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPatientCommandParser.java index d37b40a5b19..1aa397f29c2 100644 --- a/src/main/java/seedu/address/logic/parser/AddPatientCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddPatientCommandParser.java @@ -15,8 +15,10 @@ import java.util.HashSet; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Stream; +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddPatientCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.appointment.Appointment; @@ -36,7 +38,7 @@ * Parses input arguments and creates a new AddPatientCommand object */ public class AddPatientCommandParser implements Parser { - + private static final Logger logger = LogsCenter.getLogger(AddPatientCommandParser.class); /** * Parses the given {@code String} of arguments in the context of the AddPatientCommand * and returns an AddPatientCommand object for execution. @@ -44,6 +46,7 @@ public class AddPatientCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public AddPatientCommand parse(String args) throws ParseException { + logger.fine("Attempting to parse AddPatientCommand from arguments: " + args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_REMARK, PREFIX_GENDER, PREFIX_NRIC, PREFIX_CONDITION, PREFIX_BLOODTYPE, @@ -51,10 +54,11 @@ public AddPatientCommand parse(String args) throws ParseException { if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_GENDER, PREFIX_NRIC, PREFIX_CONDITION, PREFIX_BLOODTYPE, PREFIX_EMERGENCY_CONTACT) || !argMultimap.getPreamble().isEmpty()) { + logger.warning("Invalid command format for AddPatientCommand: " + args); throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPatientCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_GENDER, PREFIX_NRIC, PREFIX_CONDITION, PREFIX_BLOODTYPE, PREFIX_EMERGENCY_CONTACT); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); @@ -67,13 +71,14 @@ public AddPatientCommand parse(String args) throws ParseException { Ic ic = ParserUtil.parseIc(argMultimap.getValue(PREFIX_NRIC).get()); BloodType bloodType = ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE).get()); Condition condition = ParserUtil.parseCondition(argMultimap.getValue(PREFIX_CONDITION).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set tagList = ParserUtil.parsePatientTags(argMultimap.getAllValues(PREFIX_TAG)); // appointments need to be added separately, so we initialise patients with empty appointments Set appointmentList = new HashSet<>(); Patient patient = new Patient(name, phone, emergencyContact, email, address, remark, gender, ic, condition, bloodType, appointmentList, tagList); + logger.info("Successfully parsed AddPatientCommand with patient: " + patient); return new AddPatientCommand(patient); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index e248d12bfdd..dbaf82349ef 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -1,5 +1,6 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; @@ -13,14 +14,15 @@ import seedu.address.logic.commands.AddPatientCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteAppointmentCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindAppointmentCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.RemarkCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -43,6 +45,7 @@ public class AddressBookParser { * @throws ParseException if the user input does not conform the expected format */ public Command parseCommand(String userInput) throws ParseException { + requireNonNull(userInput); final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); @@ -69,12 +72,18 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case DeleteAppointmentCommand.COMMAND_WORD: + return new DeleteAppointmentCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FindAppointmentCommand.COMMAND_WORD: + return new FindAppointmentCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); @@ -84,9 +93,6 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); - case RemarkCommand.COMMAND_WORD: - return new RemarkCommandParser().parse(arguments); - case UndoCommand.COMMAND_WORD: return new UndoCommand(); 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 00000000000..d3f1af50645 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.DeleteAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteAppointmentCommandParser implements Parser { + private static final Logger logger = LogsCenter.getLogger(DeleteCommandParser.class); + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteAppointmentCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + try { + int index = Integer.parseInt(trimmedArgs); + logger.info("Successfully parsed integer from DeleteAppointmentCommand: " + index); + return new DeleteAppointmentCommand(index); + } catch (NumberFormatException e) { + logger.warning("Invalid user input for delete appointment command"); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAppointmentCommand.MESSAGE_USAGE), e); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index ffda571a9f8..4b91e992c54 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -2,6 +2,9 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Ic; @@ -10,17 +13,20 @@ * Parses input arguments and creates a new DeleteCommand object */ public class DeleteCommandParser implements Parser { - + private static final Logger logger = LogsCenter.getLogger(DeleteCommandParser.class); /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { try { Ic ic = ParserUtil.parseIc(args); + logger.info("Successfully parsed IC from DeleteCommand: " + ic); return new DeleteCommand(ic); } catch (ParseException pe) { + logger.warning("Invalid user input for delete command"); throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 976abc5b979..2c9c51fd093 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -1,6 +1,5 @@ package seedu.address.logic.parser; -import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; @@ -19,7 +18,9 @@ import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.logging.Logger; +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; @@ -30,14 +31,15 @@ * Parses input arguments and creates a new EditCommand object */ public class EditCommandParser implements Parser { - + public static final String CANNOT_CHANGE_IC_MESSAGE = "You cannot modify a person's IC"; + private static final Logger logger = LogsCenter.getLogger(EditCommand.class); /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { - requireNonNull(args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_REMARK, PREFIX_GENDER, PREFIX_NRIC, PREFIX_TAG, @@ -48,16 +50,18 @@ public EditCommand parse(String args) throws ParseException { try { nric = ParserUtil.parseIc(argMultimap.getPreamble()); } catch (ParseException pe) { + logger.severe("Error parsing NRIC: " + pe.getMessage()); throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_NRIC, PREFIX_GENDER, PREFIX_BLOODTYPE, PREFIX_CONDITION, PREFIX_DOCTOR, PREFIX_EMERGENCY_CONTACT); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + if (argMultimap.getValue(PREFIX_NRIC).isPresent()) { + editPersonDescriptor.setIc(ParserUtil.parseIc(argMultimap.getValue(PREFIX_NRIC).get())); + } if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } @@ -73,9 +77,6 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); } - if (argMultimap.getValue(PREFIX_NRIC).isPresent()) { - editPersonDescriptor.setIc(ParserUtil.parseIc(argMultimap.getValue(PREFIX_NRIC).get())); - } if (argMultimap.getValue(PREFIX_CONDITION).isPresent()) { editPersonDescriptor.setCondition(ParserUtil.parseCondition(argMultimap.getValue(PREFIX_CONDITION).get())); } @@ -93,10 +94,11 @@ public EditCommand parse(String args) throws ParseException { parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { + logger.warning("No fields were edited for the EditCommand."); throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - // make it pass a 3rd argument on whether doctor or patient + logger.info("Successfully parsed EditCommand."); return new EditCommand(nric, editPersonDescriptor); } diff --git a/src/main/java/seedu/address/logic/parser/FindAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/FindAppointmentCommandParser.java new file mode 100644 index 00000000000..bd67fcdad0f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindAppointmentCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.FindAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.appointment.AppointmentIcPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindAppointmentCommandParser implements Parser { + private static final Logger logger = LogsCenter.getLogger(FindAppointmentCommandParser.class); + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FindAppointmentCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + Pattern nricPattern = Pattern.compile("^[ST]\\d{7}[A-Z]$"); + Matcher nricMatcher = nricPattern.matcher(trimmedArgs); + if (trimmedArgs.isEmpty() || !nricMatcher.matches()) { + logger.warning("Can't parse find command - invalid input"); + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindAppointmentCommand.MESSAGE_USAGE)); + } + logger.info("Successfully parsed Find Command"); + AppointmentIcPredicate icPredicate = new AppointmentIcPredicate(trimmedArgs); + return new FindAppointmentCommand(icPredicate); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index d9f3320e1f1..8a7b10e2c85 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -2,6 +2,9 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -9,21 +12,25 @@ * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + private static final Logger logger = LogsCenter.getLogger(FindCommandParser.class); /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns a FindCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { + logger.warning("Can't parse find command - invalid input"); throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } String[] keywords = trimmedArgs.split("\\s+"); + logger.info("Successfully parsed Find Command"); return new FindCommand(KeywordParser.parseInput(keywords)); } diff --git a/src/main/java/seedu/address/logic/parser/KeywordParser.java b/src/main/java/seedu/address/logic/parser/KeywordParser.java index f16c02ccf8c..719572f1e2b 100644 --- a/src/main/java/seedu/address/logic/parser/KeywordParser.java +++ b/src/main/java/seedu/address/logic/parser/KeywordParser.java @@ -23,21 +23,27 @@ public class KeywordParser { public static Predicate parseInput(String[] input) { Pattern nricPattern = Pattern.compile("^[ST]\\d{7}[A-Z]$"); Pattern genderPattern = Pattern.compile("^([MF])$"); - Pattern bloodtypePattern = Pattern.compile("^Blood Type (A\\+|A-|B\\+|B-|AB\\+|AB-|O\\+|O-)$"); + Pattern bloodtypePattern = Pattern.compile("^(A\\+|A-|B\\+|B-|AB\\+|AB-|O\\+|O-)$"); Matcher genderMatcher = genderPattern.matcher(input[0]); Matcher nricMatcher = nricPattern.matcher(input[0]); - Matcher bloodtypeMatcher = bloodtypePattern.matcher(input[0]); + + if (input.length >= 3) { + Matcher bloodtypeMatcher = bloodtypePattern.matcher(input[2]); + if (bloodtypeMatcher.matches()) { + return new BloodTypePredicate(input[2]); + } else { + return new NameContainsKeywordsPredicate(Arrays.asList(input)); + } + } if (nricMatcher.matches()) { return new IcContainsKeywordsPredicate(input[0]); } else if (genderMatcher.matches()) { return new GenderPredicate(input[0]); - } else if (bloodtypeMatcher.matches()) { - return new BloodTypePredicate(input[0]); + } else { + return new NameContainsKeywordsPredicate(Arrays.asList(input)); } - - return new NameContainsKeywordsPredicate(Arrays.asList(input)); } } diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..ce644a9c6fd 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -10,6 +10,7 @@ public interface Parser { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index d853c427851..518178cebf6 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,6 +9,7 @@ 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.AppointmentTime; import seedu.address.model.person.Address; import seedu.address.model.person.BloodType; import seedu.address.model.person.Condition; @@ -135,28 +136,125 @@ public static Gender parseGender(String gender) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. + * Parses a string to create a {@code Tag} object after processing the input string. + * The input string is trimmed and converted to uppercase. If the tag name is a valid + * patient tag name, it is prefixed with "priority: " before being used to create the tag. * - * @throws ParseException if the given {@code tag} is invalid. + * @param tag The string to be parsed into a Tag object. + * @return A Tag object representing the input string. + * @throws NullPointerException if the tag parameter is null. */ - public static Tag parseTag(String tag) throws ParseException { + public static Tag parseTag(String tag) { requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + String trimmedTag = tag.trim().toUpperCase(); + if (Tag.isValidPatientTagName(trimmedTag)) { + trimmedTag = "priority: " + trimmedTag; } return new Tag(trimmedTag); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a collection of strings to create a set of {@code Tag} objects. + * Each string in the collection is processed to create a Tag. If a duplicate tag is found, + * a ParseException is thrown. + * + * @param tags The collection of strings to be parsed into Tag objects. + * @return A set of Tag objects. + * @throws ParseException if a duplicate tag is detected. + * @throws NullPointerException if the tags parameter is null. */ public static Set parseTags(Collection tags) throws ParseException { requireNonNull(tags); final Set tagSet = new HashSet<>(); for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + Tag toAdd = parseTag(tagName); + if (tagSet.contains(toAdd)) { + throw new ParseException(Tag.DUPLICATE_TAG); + } + tagSet.add(toAdd); + } + return tagSet; + } + + /** + * Parses a string to create a {@code Tag} object representing a patient tag. + * The tag name is validated to be a valid patient tag name, prefixed with "priority: ". + * If the tag name is not valid, a ParseException is thrown. + * + * @param tag The string to be parsed into a patient Tag object. + * @return A Tag object representing the patient tag. + * @throws ParseException if the tag name is not a valid patient tag name. + * @throws NullPointerException if the tag parameter is null. + */ + public static Tag parsePatientTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim().toUpperCase(); + if (!Tag.isValidPatientTagName(trimmedTag)) { + throw new ParseException(Tag.INVALID_PATIENT_TAG); + } + return new Tag("priority: " + trimmedTag); + } + + /** + * Parses a collection of strings to create a set of {@code Tag} objects representing patient tags. + * If more than one tag is provided, a ParseException is thrown since patients should only have one tag. + * Each tag is validated to be a proper patient tag. + * + * @param tags The collection of strings to be parsed into patient Tag objects. + * @return A set of Tag objects representing the patient tags. + * @throws ParseException if the collection contains more than one tag or if the tag is not valid. + * @throws NullPointerException if the tags parameter is null. + */ + public static Set parsePatientTags(Collection tags) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + if (tags.size() > 1) { + throw new ParseException(Tag.EXTRA_PATIENT_TAG); + } + for (String tagName : tags) { + tagSet.add(parsePatientTag(tagName)); + } + return tagSet; + } + + /** + * Parses a string to create a {@code Tag} object representing a doctor tag. + * The tag name is validated to be a valid doctor tag name. If the tag name is not valid, + * a ParseException is thrown. + * + * @param tag The string to be parsed into a doctor Tag object. + * @return A Tag object representing the doctor tag. + * @throws ParseException if the tag name is not a valid doctor tag name. + * @throws NullPointerException if the tag parameter is null. + */ + public static Tag parseDoctorTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim().toUpperCase(); + if (!Tag.isValidDoctorTagName(trimmedTag)) { + throw new ParseException(Tag.INVALID_DOCTOR_TAG); + } + return new Tag(trimmedTag); + } + + /** + * Parses a collection of strings to create a set of {@code Tag} objects representing doctor tags. + * Duplicate tags are not allowed, and if found, a ParseException is thrown. + * Each tag is validated to be a proper doctor tag. + * + * @param tags The collection of strings to be parsed into doctor Tag objects. + * @return A set of Tag objects representing the doctor tags. + * @throws ParseException if a duplicate tag is detected or if the tag is not valid. + * @throws NullPointerException if the tags parameter is null. + */ + public static Set parseDoctorTags(Collection tags) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + Tag toAdd = parseDoctorTag(tagName); + if (tagSet.contains(toAdd)) { + throw new ParseException(Tag.DUPLICATE_TAG); + } + tagSet.add(toAdd); } return tagSet; } @@ -201,4 +299,17 @@ public static Remark parseRemark(String remark) { String trimmedRemark = remark.trim(); return new Remark(trimmedRemark); } + + /** + * Parses {@code String appointmentTime} into a {@code Appointment}. + * Leading and trailing whitespaces will be trimmed. + */ + public static AppointmentTime parseAppointmentTime(String appointmentTime) throws ParseException { + requireNonNull(appointmentTime); + String trimmedAppointmentTime = appointmentTime.trim(); + if (!AppointmentTime.isValidAppointmentTime(trimmedAppointmentTime)) { + throw new ParseException(AppointmentTime.MESSAGE_CONSTRAINTS); + } + return new AppointmentTime(trimmedAppointmentTime); + } } diff --git a/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java deleted file mode 100644 index ab4093c5b78..00000000000 --- a/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.RemarkCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Remark; - -/** - * The DeleteCommandParser class is responsible for parsing user input and creating a DeleteCommand object - * to represent the user's request to delete a record. It interprets the provided input arguments and converts them - * into a valid DeleteCommand. - */ -public class RemarkCommandParser implements Parser { - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * - * @param args The user input string containing the details for deleting a record. - * @return A DeleteCommand object representing the user's request to delete a record. - * @throws ParseException If the user input does not conform to the expected format. - */ - public RemarkCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, - PREFIX_REMARK); - - Index index; - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (IllegalValueException ive) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, - RemarkCommand.MESSAGE_USAGE), ive); - } - - String remarkString = argMultimap.getValue(PREFIX_REMARK).orElse(""); - Remark remark = new Remark(remarkString); - - return new RemarkCommand(index, remark); - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 710ec25792d..9cca0d3e9e9 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -7,8 +7,11 @@ import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; +import seedu.address.model.person.Ic; import seedu.address.model.person.Patient; +import seedu.address.model.person.UniqueAppointmentList; import seedu.address.model.person.UniqueDoctorList; import seedu.address.model.person.UniquePatientList; @@ -19,6 +22,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniqueDoctorList doctors; private final UniquePatientList patients; + private final UniqueAppointmentList appointments; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -30,6 +34,7 @@ public class AddressBook implements ReadOnlyAddressBook { { doctors = new UniqueDoctorList(); patients = new UniquePatientList(); + appointments = new UniqueAppointmentList(); } public AddressBook() { @@ -50,7 +55,7 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { * {@code patients} must not contain duplicate patients. */ public void setPatients(List patients) { - this.patients.setPatients(patients); + this.patients.setObjects(patients); } /** @@ -58,7 +63,15 @@ public void setPatients(List patients) { * {@code doctors} must not contain duplicate persons. */ public void setDoctors(List doctors) { - this.doctors.setDoctors(doctors); + this.doctors.setObjects(doctors); + } + + /** + * Replaces the contents of the appointments list with {@code appointments}. + * {@code appointments} must not contain duplicate appointments. + */ + public void setAppointments(List appointments) { + this.appointments.setObjects(appointments); } /** @@ -68,6 +81,7 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setDoctors(newData.getDoctorList()); setPatients(newData.getPatientList()); + setAppointments(newData.getAppointmentList()); } //// person-level operations @@ -79,6 +93,7 @@ public boolean hasPatient(Patient patient) { requireNonNull(patient); // return in patients list or in doctor list return patients.contains(patient); + } /** @@ -89,6 +104,14 @@ public boolean hasDoctor(Doctor doctor) { return doctors.contains(doctor); } + /** + * Returns true if an appointment with the same details as {@code appointment} exists in the address book. + */ + public boolean hasAppointment(Appointment appointment) { + requireNonNull(appointment); + return appointments.contains(appointment); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -107,6 +130,10 @@ public void addDoctor(Doctor d) { doctors.add(d); } + public void addAppointment(Appointment a) { + appointments.add(a); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * Replaces the given patients {@code target} in the list with {@code editedPerson}. @@ -116,7 +143,7 @@ public void addDoctor(Doctor d) { public void setPatient(Patient target, Patient editedPerson) { requireNonNull(editedPerson); - patients.setPatient(target, editedPerson); + patients.setObject(target, editedPerson); } /** @@ -127,7 +154,7 @@ public void setPatient(Patient target, Patient editedPerson) { public void setDoctor(Doctor target, Doctor editedDoctor) { requireNonNull(editedDoctor); - doctors.setDoctor(target, editedDoctor); + doctors.setObject(target, editedDoctor); } /** @@ -150,6 +177,16 @@ public void removeDoctor(Doctor key) { } } + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeAppointment(Appointment key) { + if (appointments.contains(key)) { + appointments.remove(key); + } + } + //// util methods @Override @@ -157,6 +194,7 @@ public String toString() { //not sure how to modify this return new ToStringBuilder(this) .add("patients", patients) .add("doctors", doctors) + .add("appointments", appointments) .toString(); } @@ -164,11 +202,14 @@ public ObservableList getPatientList() { return patients.asUnmodifiableObservableList(); } - @Override public ObservableList getDoctorList() { return doctors.asUnmodifiableObservableList(); } + public ObservableList getAppointmentList() { + return appointments.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -182,11 +223,19 @@ public boolean equals(Object other) { AddressBook otherAddressBook = (AddressBook) other; return patients.equals(otherAddressBook.patients) - && doctors.equals((otherAddressBook.doctors)); + && doctors.equals((otherAddressBook.doctors)) + && appointments.equals((otherAddressBook.appointments)); } @Override public int hashCode() { - return Objects.hash(patients, doctors); + return Objects.hash(patients, doctors, appointments); + } + + /** + * Checks if an IC exists in the addressbook. + */ + public boolean hasIc(Ic nric) { + return patients.containsIc(nric) || doctors.containsIc(nric); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index e41f750f289..4b74023ca77 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -6,7 +6,9 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; +import seedu.address.model.person.Ic; import seedu.address.model.person.Patient; import seedu.address.model.person.Person; @@ -20,6 +22,8 @@ public interface Model { Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; Predicate PREDICATE_SHOW_ALL_DOCTORS = unused -> true; + Predicate PREDICATE_SHOW_ALL_APPOINTMENTS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -77,6 +81,8 @@ public interface Model { */ void addPerson(Person person); + void deleteAppointment(Appointment appointment); + /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. @@ -84,10 +90,7 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); - /** - * Returns an unmodifiable view of the filtered person list - */ - ObservableList getFilteredPersonList(); + void addAppointment(Appointment appointment); /** * Returns an unmodifiable view of the filtered patient list @@ -99,6 +102,8 @@ public interface Model { */ ObservableList getFilteredDoctorList(); + ObservableList getFilteredAppointmentList(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @@ -109,4 +114,7 @@ public interface Model { void undo() throws CommandException; void redo() throws CommandException; + + void updateFilteredAppointmentList(Predicate predicate); + boolean hasIc(Ic nric); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index c2205937d27..39eec66c5d3 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -15,7 +15,9 @@ import seedu.address.logic.commands.RedoCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; +import seedu.address.model.person.Ic; import seedu.address.model.person.Patient; import seedu.address.model.person.Person; @@ -31,6 +33,7 @@ public class ModelManager implements Model { private final UserPrefs userPrefs; private final FilteredList filteredDoctors; private final FilteredList filteredPatients; + private final FilteredList filteredAppointments; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -45,6 +48,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.redoList = new ArrayList<>(); filteredDoctors = new FilteredList<>(this.addressBook.getDoctorList()); filteredPatients = new FilteredList<>(this.addressBook.getPatientList()); + filteredAppointments = new FilteredList<>(this.addressBook.getAppointmentList()); } public ModelManager() { @@ -129,14 +133,16 @@ public ReadOnlyAddressBook getAddressBook() { @Override public boolean hasPerson(Person person) { requireNonNull(person); - if (person instanceof Patient) { + if (person.isPatient()) { return addressBook.hasPatient((Patient) person); - } else if (person instanceof Doctor) { - return addressBook.hasDoctor((Doctor) person); } else { - return false; + return addressBook.hasDoctor((Doctor) person); } } + @Override + public boolean hasIc(Ic nric) { + return addressBook.hasIc(nric); + } @Override public void deletePerson(Person target) { @@ -152,16 +158,30 @@ public void deletePerson(Person target) { @Override public void addPerson(Person person) { - if (person instanceof Patient) { + if (person.isPatient()) { updateBackup(); addressBook.addPatient((Patient) person); - } else if (person instanceof Doctor) { + } else if (person.isDoctor()) { updateBackup(); addressBook.addDoctor((Doctor) person); } updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } + @Override + public void addAppointment(Appointment appointment) { + updateBackup(); + addressBook.addAppointment(appointment); + updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + } + + @Override + public void deleteAppointment(Appointment appointment) { + updateBackup(); + addressBook.removeAppointment(appointment); + updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + } + @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); @@ -174,13 +194,6 @@ public void setPerson(Person target, Person editedPerson) { } } - /** - * Returns an unmodifiable view of the filtered person list - */ - @Override - public ObservableList getFilteredPersonList() { - return null; - } //=========== Filtered Person List Accessors ============================================================= @@ -201,6 +214,11 @@ public ObservableList getFilteredDoctorList() { return filteredDoctors; } + @Override + public ObservableList getFilteredAppointmentList() { + return filteredAppointments; + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); @@ -208,6 +226,12 @@ public void updateFilteredPersonList(Predicate predicate) { filteredDoctors.setPredicate(predicate); } + @Override + public void updateFilteredAppointmentList(Predicate predicate) { + requireNonNull(predicate); + filteredAppointments.setPredicate(predicate); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -223,6 +247,8 @@ public boolean equals(Object other) { return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) && filteredDoctors.equals(otherModelManager.filteredDoctors) - && filteredPatients.equals(otherModelManager.filteredPatients); + && filteredPatients.equals(otherModelManager.filteredPatients) + && filteredAppointments.equals(otherModelManager.filteredAppointments); } + } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 57e49cad716..bacc136c5e6 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; import seedu.address.model.person.Patient; @@ -14,4 +15,5 @@ public interface ReadOnlyAddressBook { */ ObservableList getDoctorList(); ObservableList getPatientList(); + ObservableList getAppointmentList(); } diff --git a/src/main/java/seedu/address/model/appointment/Appointment.java b/src/main/java/seedu/address/model/appointment/Appointment.java index 036f80be1d6..b702ab733af 100644 --- a/src/main/java/seedu/address/model/appointment/Appointment.java +++ b/src/main/java/seedu/address/model/appointment/Appointment.java @@ -2,8 +2,6 @@ import static java.util.Objects.requireNonNull; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.Objects; import seedu.address.model.person.Ic; @@ -14,11 +12,9 @@ * Doctor and patient ic should not be the same. */ public class Appointment { - public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private Ic doctorIc; private Ic patientIc; - private LocalDateTime appointmentTime; - private String status = "scheduled"; + private AppointmentTime appointmentTime; /** * Constructs a new appointment with the specified doctor, patient, and appointment time. @@ -27,7 +23,7 @@ public class Appointment { * @param patientIc The patient involved in the appointment. * @param appointmentTime The date and time of the appointment. */ - public Appointment(Ic doctorIc, Ic patientIc, LocalDateTime appointmentTime) { + public Appointment(Ic doctorIc, Ic patientIc, AppointmentTime appointmentTime) { requireNonNull(doctorIc); requireNonNull(patientIc); requireNonNull(appointmentTime); @@ -44,7 +40,7 @@ public Appointment(Ic doctorIc, Ic patientIc, LocalDateTime appointmentTime) { * @param appointmentTime The date and time of the appointment. * @param status The status of the appointment. */ - public Appointment(Ic doctorIc, Ic patientIc, LocalDateTime appointmentTime, String status) { + public Appointment(Ic doctorIc, Ic patientIc, AppointmentTime appointmentTime, String status) { requireNonNull(doctorIc); requireNonNull(patientIc); requireNonNull(appointmentTime); @@ -52,10 +48,9 @@ public Appointment(Ic doctorIc, Ic patientIc, LocalDateTime appointmentTime, Str this.doctorIc = doctorIc; this.patientIc = patientIc; this.appointmentTime = appointmentTime; - this.status = status; } - public LocalDateTime getAppointmentTime() { + public AppointmentTime getAppointmentTime() { return appointmentTime; } @@ -67,11 +62,7 @@ public Ic getPatient() { return patientIc; } - public String getStatus() { - return status; - } - - public void setAppointmentTime(LocalDateTime appointmentTime) { + public void setAppointmentTime(AppointmentTime appointmentTime) { this.appointmentTime = appointmentTime; } @@ -83,10 +74,6 @@ public void changePatient(Ic newPatientIc) { this.patientIc = newPatientIc; } - public void changeStatus(String newStatus) { - this.status = newStatus; - } - @Override public boolean equals(Object other) { if (other == this) { @@ -101,18 +88,17 @@ public boolean equals(Object other) { Appointment otherAppointment = (Appointment) other; return this.doctorIc.equals(otherAppointment.doctorIc) && this.patientIc.equals(otherAppointment.patientIc) - && this.appointmentTime.equals(otherAppointment.appointmentTime) - && this.status.equals(otherAppointment.status); + && this.appointmentTime.equals(otherAppointment.appointmentTime); } @Override public int hashCode() { - return Objects.hash(doctorIc, patientIc, appointmentTime, status); + return Objects.hash(doctorIc, patientIc, appointmentTime); } @Override public String toString() { return "Patient with IC " + patientIc + ", Doctor with IC " + doctorIc + " at " - + appointmentTime.format(FORMATTER); + + appointmentTime.toString(); } } diff --git a/src/main/java/seedu/address/model/appointment/AppointmentIcPredicate.java b/src/main/java/seedu/address/model/appointment/AppointmentIcPredicate.java new file mode 100644 index 00000000000..0c7bda2655f --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/AppointmentIcPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.appointment; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Checks if an {@code appointment} has a patient or doctor, matching + * the query Ic {@code keywords}. + */ +public class AppointmentIcPredicate implements Predicate { + private final String keywords; + + public AppointmentIcPredicate(String keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Appointment appointment) { + if (keywords.equalsIgnoreCase(appointment.getPatient().toString())) { + return true; + } + return keywords.equalsIgnoreCase(appointment.getDoctor().toString()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AppointmentIcPredicate)) { + return false; + } + + AppointmentIcPredicate otherAppointmentIcPredicate = (AppointmentIcPredicate) other; + return keywords.equals(otherAppointmentIcPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} + diff --git a/src/main/java/seedu/address/model/appointment/AppointmentTime.java b/src/main/java/seedu/address/model/appointment/AppointmentTime.java new file mode 100644 index 00000000000..c997fec687c --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/AppointmentTime.java @@ -0,0 +1,76 @@ +package seedu.address.model.appointment; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + + +/** + * Represents the time of an appointment. + * Guarantees: immutable; is valid as declared in {@link #isValidAppointmentTime(String)} + */ +public class AppointmentTime implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "AppointmentTime should be valid date and time in the format of yyyy-mm-dd HH:mm:ss\n"; + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + public final LocalDateTime value; + + /** + * Constructs a {@code AppointmentTime}. + * + * @param appointmentTime A valid ic. + */ + public AppointmentTime(String appointmentTime) { + requireNonNull(appointmentTime); + checkArgument(isValidAppointmentTime(appointmentTime), MESSAGE_CONSTRAINTS); + value = LocalDateTime.parse(appointmentTime, FORMATTER); + } + + /** + * Returns true if a given string is a valid appointmentTime. + */ + public static boolean isValidAppointmentTime(String test) { + try { + LocalDateTime.parse(test, FORMATTER); + } catch (DateTimeParseException e) { + return false; + } + return true; + } + + @Override + public String toString() { + return value.format(FORMATTER); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AppointmentTime)) { + return false; + } + + AppointmentTime otherAppointmentTime = (AppointmentTime) other; + return value.equals(otherAppointmentTime.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(AppointmentTime o) { + return this.value.compareTo(o.value); + } +} diff --git a/src/main/java/seedu/address/model/person/BloodTypePredicate.java b/src/main/java/seedu/address/model/person/BloodTypePredicate.java index bc900dfab3c..c119fb8e6e7 100644 --- a/src/main/java/seedu/address/model/person/BloodTypePredicate.java +++ b/src/main/java/seedu/address/model/person/BloodTypePredicate.java @@ -14,8 +14,7 @@ public class BloodTypePredicate implements Predicate { * Constructs a predicate that tests that a {@code Person}'s {@code Gender} matches either male or female. */ public BloodTypePredicate(String keywords) { - String[] trimmed = keywords.split(" "); - this.keywords = trimmed[2]; + this.keywords = keywords; } @Override diff --git a/src/main/java/seedu/address/model/person/GenderPredicate.java b/src/main/java/seedu/address/model/person/GenderPredicate.java index 854709ab9ac..98203a13e5d 100644 --- a/src/main/java/seedu/address/model/person/GenderPredicate.java +++ b/src/main/java/seedu/address/model/person/GenderPredicate.java @@ -38,4 +38,6 @@ public boolean equals(Object other) { public String toString() { return new ToStringBuilder(this).add("keywords", keywords).toString(); } + + } diff --git a/src/main/java/seedu/address/model/person/Ic.java b/src/main/java/seedu/address/model/person/Ic.java index 2217fd9b168..1ae41880bf2 100644 --- a/src/main/java/seedu/address/model/person/Ic.java +++ b/src/main/java/seedu/address/model/person/Ic.java @@ -12,7 +12,7 @@ public class Ic { public static final String MESSAGE_CONSTRAINTS = "Ic should start with S or T, followed by 7 numbers, and ends with a letter. " - + "Letters must be in all caps. Empty strings are not allowed\n"; + + "Letters inputs are case-insensitive. Empty strings are not allowed\n"; public static final String VALIDATION_REGEX = "^[TS]\\d{7}[A-Z]$"; public final String value; @@ -58,6 +58,4 @@ public boolean equals(Object other) { public int hashCode() { return value.hashCode(); } - - } diff --git a/src/main/java/seedu/address/model/person/Patient.java b/src/main/java/seedu/address/model/person/Patient.java index ca4a11a46c2..e2dcef75cbc 100644 --- a/src/main/java/seedu/address/model/person/Patient.java +++ b/src/main/java/seedu/address/model/person/Patient.java @@ -95,7 +95,8 @@ public boolean equals(Object other) { @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, gender, ic, condition, bloodType, appointments, tags); + return Objects.hash(name, phone, emergencyContact, email, address, gender, ic, condition, bloodType, + appointments, tags); } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index a879e48f07f..69d2a42eb9c 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.time.LocalDateTime; import java.util.Collections; import java.util.HashSet; import java.util.Objects; @@ -11,13 +10,14 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentTime; import seedu.address.model.tag.Tag; /** * Represents a Person in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ -public class Person { +public abstract class Person { // Identity fields protected final Name name; @@ -111,7 +111,7 @@ public boolean hasIc(Ic ic) { * @param dateTime the time of appointment to be checked. * @return true is this person has an appointment at the specified time. */ - public boolean hasAppointmentAt(LocalDateTime dateTime) { + public boolean hasAppointmentAt(AppointmentTime dateTime) { requireNonNull(dateTime); // check from set of appointments for (Appointment a : appointments) { @@ -131,6 +131,15 @@ public void addAppointment(Appointment appointment) { this.appointments.add(appointment); } + /** + * Removes the given {@Code appointment} to this person's set of appointments. + * + * @param appointment the appointment to be added. + */ + public void deleteAppointment(Appointment appointment) { + this.appointments.remove(appointment); + } + /** * Retrieves the list of patients stored in this medical facility. * @@ -200,5 +209,4 @@ public String toString() { .add("tags", tags) .toString(); } - } diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniqueAppointmentList.java similarity index 53% rename from src/main/java/seedu/address/model/person/UniquePersonList.java rename to src/main/java/seedu/address/model/person/UniqueAppointmentList.java index 22dc48af2b6..d3e68ac7843 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniqueAppointmentList.java @@ -3,13 +3,14 @@ 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.Appointment; +import seedu.address.model.person.exceptions.DuplicateObjectException; import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.person.exceptions.ObjectNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. @@ -22,28 +23,35 @@ * * @see Person#isSamePerson(Person) */ -public class UniquePersonList implements Iterable { +public class UniqueAppointmentList extends UniqueObjectList { - protected final ObservableList internalList = FXCollections.observableArrayList(); - protected final ObservableList internalUnmodifiableList = + protected final ObservableList internalList = FXCollections.observableArrayList(); + protected final ObservableList internalUnmodifiableList = FXCollections.unmodifiableObservableList(internalList); + public void setAppointments(UniqueAppointmentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + /** * Returns true if the list contains an equivalent person as the given argument. */ - public boolean contains(Person toCheck) { + @Override + public boolean contains(Appointment toCheck) { requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); + return internalList.stream().anyMatch(toCheck::equals); } /** * Adds a person to the list. * The person must not already exist in the list. */ - public void add(Person toAdd) { + @Override + public void add(Appointment toAdd) { requireNonNull(toAdd); if (contains(toAdd)) { - throw new DuplicatePersonException(); + throw new DuplicateObjectException(); } internalList.add(toAdd); } @@ -53,94 +61,61 @@ public void add(Person toAdd) { * {@code target} must exist in the list. * The person identity of {@code editedPerson} must not be the same as another existing person in the list. */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + @Override + public void setObject(Appointment target, Appointment editedAppointment) { + requireAllNonNull(target, editedAppointment); int index = internalList.indexOf(target); if (index == -1) { - throw new PersonNotFoundException(); + throw new ObjectNotFoundException(); } - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { + if (!target.equals(editedAppointment) && contains(editedAppointment)) { throw new DuplicatePersonException(); } - internalList.set(index, editedPerson); + internalList.set(index, editedAppointment); } /** * Removes the equivalent person from the list. * The person must exist in the list. */ - public void remove(Person toRemove) { + @Override + public void remove(Appointment toRemove) { requireNonNull(toRemove); if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); + throw new ObjectNotFoundException(); } } - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - /** * Replaces the contents of this list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof UniquePersonList)) { - return false; + public void setObjects(List appointments) { + requireAllNonNull(appointments); + if (!objectsAreUnique(appointments)) { + throw new DuplicateObjectException(); } - UniquePersonList otherUniquePersonList = (UniquePersonList) other; - return internalList.equals(otherUniquePersonList.internalList); - } - - @Override - public int hashCode() { - return internalList.hashCode(); + internalList.setAll(appointments); } @Override - public String toString() { - return internalList.toString(); + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; } /** * Returns true if {@code persons} contains only unique persons. */ - protected boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { + @Override + protected boolean objectsAreUnique(List appointments) { + for (int i = 0; i < appointments.size() - 1; i++) { + for (int j = i + 1; j < appointments.size(); j++) { + if (appointments.get(i).equals(appointments.get(j))) { return false; } } diff --git a/src/main/java/seedu/address/model/person/UniqueDoctorList.java b/src/main/java/seedu/address/model/person/UniqueDoctorList.java index 287225e3d1f..d59f6384018 100644 --- a/src/main/java/seedu/address/model/person/UniqueDoctorList.java +++ b/src/main/java/seedu/address/model/person/UniqueDoctorList.java @@ -3,7 +3,6 @@ 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; @@ -22,24 +21,38 @@ * * @see Person#isSamePerson(Person) */ -public class UniqueDoctorList implements Iterable { +public class UniqueDoctorList extends UniqueObjectList { - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = + protected final ObservableList internalList = FXCollections.observableArrayList(); + protected final ObservableList internalUnmodifiableList = FXCollections.unmodifiableObservableList(internalList); + public void setDoctors(UniqueDoctorList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + /** * Returns true if the list contains an equivalent person as the given argument. */ + @Override public boolean contains(Doctor toCheck) { requireNonNull(toCheck); return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains the same ic as the given argument. + */ + public boolean containsIc(Ic nric) { + requireNonNull(nric); + return internalList.stream().anyMatch(x -> x.getIc().equals(nric)); + } /** * Adds a person to the list. * The person must not already exist in the list. */ + @Override public void add(Doctor toAdd) { requireNonNull(toAdd); if (contains(toAdd)) { @@ -49,29 +62,31 @@ public void add(Doctor toAdd) { } /** - * Replaces the doctor {@code target} in the list with {@code editedPerson}. + * Replaces the person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the list. * The person identity of {@code editedPerson} must not be the same as another existing person in the list. */ - public void setDoctor(Doctor target, Doctor editedPerson) { - requireAllNonNull(target, editedPerson); + @Override + public void setObject(Doctor target, Doctor editedDoctor) { + requireAllNonNull(target, editedDoctor); int index = internalList.indexOf(target); if (index == -1) { throw new PersonNotFoundException(); } - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { + if (!target.isSamePerson(editedDoctor) && contains(editedDoctor)) { throw new DuplicatePersonException(); } - internalList.set(index, editedPerson); + internalList.set(index, editedDoctor); } /** * Removes the equivalent person from the list. * The person must exist in the list. */ + @Override public void remove(Doctor toRemove) { requireNonNull(toRemove); if (!internalList.remove(toRemove)) { @@ -79,81 +94,33 @@ public void remove(Doctor toRemove) { } } - public void setDoctors(UniqueDoctorList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - /** * Replaces the contents of this list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setDoctors(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { + @Override + public void setObjects(List doctors) { + requireAllNonNull(doctors); + if (!objectsAreUnique(doctors)) { throw new DuplicatePersonException(); } - internalList.setAll(persons); + internalList.setAll(doctors); } - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ + @Override public ObservableList asUnmodifiableObservableList() { return internalUnmodifiableList; } - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof UniqueDoctorList)) { - return false; - } - - UniqueDoctorList otherUniqueDoctorList = (UniqueDoctorList) other; - - // Check if both lists are of the same size - if (this.internalList.size() != otherUniqueDoctorList.internalList.size()) { - return false; - } - - // Compare individual doctors in the lists - for (int i = 0; i < this.internalList.size(); i++) { - if (!this.internalList.get(i).equals(otherUniqueDoctorList.internalList.get(i))) { - return false; - } - } - - return true; - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - @Override - public String toString() { - return internalList.toString(); - } - /** * Returns true if {@code persons} contains only unique persons. */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { + @Override + protected boolean objectsAreUnique(List doctors) { + for (int i = 0; i < doctors.size() - 1; i++) { + for (int j = i + 1; j < doctors.size(); j++) { + if (doctors.get(i).isSamePerson(doctors.get(j))) { return false; } } @@ -161,3 +128,4 @@ private boolean personsAreUnique(List persons) { return true; } } + diff --git a/src/main/java/seedu/address/model/person/UniqueObjectList.java b/src/main/java/seedu/address/model/person/UniqueObjectList.java new file mode 100644 index 00000000000..433595f2b19 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueObjectList.java @@ -0,0 +1,96 @@ +package seedu.address.model.person; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is + * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so + * as to ensure that the person with exactly the same fields will be removed. + *

+ * Supports a minimal set of list operations. + * + * @see Person#isSamePerson(Person) + */ +public abstract class UniqueObjectList implements Iterable { + + protected final ObservableList internalList = FXCollections.observableArrayList(); + protected final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public abstract boolean contains(T toCheck); + + /** + * Adds a person to the list. + * The person must not already exist in the list. + */ + public abstract void add(T toAdd); + + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The person identity of {@code editedPerson} must not be the same as another existing person in the list. + */ + public abstract void setObject(T target, T editedObject); + + /** + * Removes the equivalent person from the list. + * The person must exist in the list. + */ + public abstract void remove(T toRemove); + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public abstract void setObjects(List objects); + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public abstract ObservableList asUnmodifiableObservableList(); + + @Override + public Iterator iterator() { + return (Iterator) internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueObjectList)) { + return false; + } + + UniqueObjectList otherUniqueObjectList = (UniqueObjectList) other; + return internalList.equals(otherUniqueObjectList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code persons} contains only unique persons. + */ + protected abstract boolean objectsAreUnique(List objects); +} + diff --git a/src/main/java/seedu/address/model/person/UniquePatientList.java b/src/main/java/seedu/address/model/person/UniquePatientList.java index 0094b9c25c8..35c61e38d55 100644 --- a/src/main/java/seedu/address/model/person/UniquePatientList.java +++ b/src/main/java/seedu/address/model/person/UniquePatientList.java @@ -3,7 +3,6 @@ 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; @@ -22,24 +21,38 @@ * * @see Person#isSamePerson(Person) */ -public class UniquePatientList implements Iterable { +public class UniquePatientList extends UniqueObjectList { protected final ObservableList internalList = FXCollections.observableArrayList(); protected final ObservableList internalUnmodifiableList = FXCollections.unmodifiableObservableList(internalList); + public void setPersons(UniquePatientList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + /** * Returns true if the list contains an equivalent person as the given argument. */ + @Override public boolean contains(Patient toCheck) { requireNonNull(toCheck); return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains an equivalent ic as the given argument. + */ + public boolean containsIc(Ic nric) { + requireNonNull(nric); + return internalList.stream().anyMatch(patient -> patient.getIc().equals(nric)); + } /** * Adds a person to the list. * The person must not already exist in the list. */ + @Override public void add(Patient toAdd) { requireNonNull(toAdd); if (contains(toAdd)) { @@ -49,29 +62,31 @@ public void add(Patient toAdd) { } /** - * Replaces the patient {@code target} in the list with {@code editedPerson}. + * Replaces the person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the list. * The person identity of {@code editedPerson} must not be the same as another existing person in the list. */ - public void setPatient(Patient target, Patient editedPerson) { - requireAllNonNull(target, editedPerson); + @Override + public void setObject(Patient target, Patient editedPatient) { + requireAllNonNull(target, editedPatient); int index = internalList.indexOf(target); if (index == -1) { throw new PersonNotFoundException(); } - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { + if (!target.isSamePerson(editedPatient) && contains(editedPatient)) { throw new DuplicatePersonException(); } - internalList.set(index, editedPerson); + internalList.set(index, editedPatient); } /** * Removes the equivalent person from the list. * The person must exist in the list. */ + @Override public void remove(Patient toRemove) { requireNonNull(toRemove); if (!internalList.remove(toRemove)) { @@ -79,79 +94,33 @@ public void remove(Patient toRemove) { } } - public void setPatients(UniquePatientList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - /** * Replaces the contents of this list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setPatients(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { + @Override + public void setObjects(List patients) { + requireAllNonNull(patients); + if (!objectsAreUnique(patients)) { throw new DuplicatePersonException(); } - internalList.setAll(persons); + internalList.setAll(patients); } - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ + @Override public ObservableList asUnmodifiableObservableList() { return internalUnmodifiableList; } - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof UniquePatientList)) { - return false; - } - - UniquePatientList otherUniquePatientList = (UniquePatientList) other; - - if (this.internalList.size() != otherUniquePatientList.internalList.size()) { - return false; - } - - // Compare individual patients in the lists - for (int i = 0; i < this.internalList.size(); i++) { - if (!this.internalList.get(i).equals(otherUniquePatientList.internalList.get(i))) { - return false; - } - } - return true; - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - @Override - public String toString() { - return internalList.toString(); - } - /** * Returns true if {@code persons} contains only unique persons. */ - protected boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { + @Override + protected boolean objectsAreUnique(List patients) { + for (int i = 0; i < patients.size() - 1; i++) { + for (int j = i + 1; j < patients.size(); j++) { + if (patients.get(i).isSamePerson(patients.get(j))) { return false; } } diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateObjectException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateObjectException.java new file mode 100644 index 00000000000..6a88bc60702 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateObjectException.java @@ -0,0 +1,10 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation will result in duplicate objects. + */ +public class DuplicateObjectException extends RuntimeException { + public DuplicateObjectException() { + super("Operation would result in duplicate objects"); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/ObjectNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/ObjectNotFoundException.java new file mode 100644 index 00000000000..c176f7a1b95 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/ObjectNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.person.exceptions; + +/** + * Signals that the operation is unable to find the specified object. + */ +public class ObjectNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index f1a0d4e233b..aa809e36aaa 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,16 +1,18 @@ package seedu.address.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} + * Guarantees: immutable; name is valid as declared in {@link #isValidPatientTagName(String)} */ public class Tag { - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String DUPLICATE_TAG = "Doctors should not have duplicate tags!"; + public static final String EXTRA_PATIENT_TAG = "Each Patient should only have one priority tag!"; + public static final String INVALID_PATIENT_TAG = "Patient tag should be a valid priority level: Low, Medium or" + + " High."; + public static final String INVALID_DOCTOR_TAG = "Doctor tag should be a valid specialisation."; public final String tagName; @@ -21,15 +23,52 @@ public class Tag { */ public Tag(String tagName) { requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); this.tagName = tagName; } /** - * Returns true if a given string is a valid tag name. + * Returns true if a given string is a valid patient tag name. */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); + public static boolean isValidPatientTagName(String test) { + for (ValidPatientTags tag : ValidPatientTags.values()) { + if (tag.name().equals(test)) { + return true; + } + } + return false; + } + + /** + * Returns true if a given string is a valid patient tag name. + */ + public static boolean isValidFullPatientTagName(String test) { + for (ValidPatientTags tag : ValidPatientTags.values()) { + String fullTagName = "priority: " + tag; + if (fullTagName.equals(test)) { + return true; + } + } + return false; + } + + /** + * Returns true if a given string is a valid doctor tag name. + */ + public static boolean isValidDoctorTagName(String test) { + for (ValidDoctorTags tag : ValidDoctorTags.values()) { + if (tag.name().equals(test)) { + return true; + } + } + return false; + } + + public boolean isValidPatientTag() { + return isValidFullPatientTagName(tagName); + } + + public boolean isValidDoctorTag() { + return isValidDoctorTagName(tagName); } @Override diff --git a/src/main/java/seedu/address/model/tag/ValidDoctorTags.java b/src/main/java/seedu/address/model/tag/ValidDoctorTags.java new file mode 100644 index 00000000000..9edea624241 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/ValidDoctorTags.java @@ -0,0 +1,9 @@ +package seedu.address.model.tag; + +/** + * Enumeration of valid doctor specializations for doctor tags. + * These specializations are used to categorize doctors by their field of expertise. + */ +public enum ValidDoctorTags { + CARDIOLOGIST, ORTHOPEDIC, PEDIATRICIAN, DERMATOLOGIST, NEUROLOGIST, GENERAL_PRACTITIONER, PSYCHIATRIST, SURGEON +} diff --git a/src/main/java/seedu/address/model/tag/ValidPatientTags.java b/src/main/java/seedu/address/model/tag/ValidPatientTags.java new file mode 100644 index 00000000000..f41e4417842 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/ValidPatientTags.java @@ -0,0 +1,8 @@ +package seedu.address.model.tag; + +/** + * Enumeration of valid priority levels for patient tags. + */ +public enum ValidPatientTags { + LOW, MEDIUM, HIGH +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 110b04ce12e..2ab58e94d91 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,6 +1,5 @@ package seedu.address.model.util; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -9,6 +8,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentTime; import seedu.address.model.person.Address; import seedu.address.model.person.BloodType; import seedu.address.model.person.Condition; @@ -30,11 +30,11 @@ public class SampleDataUtil { public static final Remark EMPTY_REMARK = new Remark(""); public static final Set EMPTY_APPOINTMENTS = new HashSet<>(); public static final Appointment APPOINTMENT_1 = new Appointment(new Ic("S8811111Z"), - new Ic("S1111111Z"), LocalDateTime.parse("2023-10-31T14:00")); + new Ic("S1111111Z"), new AppointmentTime("2023-10-31 14:00")); public static final Appointment APPOINTMENT_2 = new Appointment(new Ic("S8811112Z"), - new Ic("S1111111Z"), LocalDateTime.parse("2023-10-31T15:00")); + new Ic("S1111111Z"), new AppointmentTime("2023-10-31 15:00")); public static final Appointment APPOINTMENT_3 = new Appointment(new Ic("S8811112Z"), - new Ic("S1111112Z"), LocalDateTime.parse("2023-10-31T16:00")); + new Ic("S1111112Z"), new AppointmentTime("2023-10-31 16:00")); // persons should not be used anymore public static Patient[] getSamplePatients() { @@ -43,30 +43,30 @@ public static Patient[] getSamplePatients() { new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_REMARK, new Gender("M"), new Ic("S1111111Z"), new Condition("Unknown"), new BloodType("O+"), getAppointmentSet(APPOINTMENT_1, APPOINTMENT_2), - getTagSet("friends")), + getTagSet("priority: LOW")), new Patient(new Name("Bernice Yu"), new Phone("99272758"), new Phone("87438807"), new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_REMARK, new Gender("F"), new Ic("S1111112Z"), new Condition("Unknown"), new BloodType("O+"), getAppointmentSet(APPOINTMENT_3), - getTagSet("colleagues", "friends")), + getTagSet("priority: LOW")), new Patient(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Phone("87438807"), new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), EMPTY_REMARK, new Gender("F"), new Ic("S1111113Z"), new Condition("Unknown"), new BloodType("O+"), EMPTY_APPOINTMENTS, - getTagSet("neighbours")), + getTagSet("priority: MEDIUM")), new Patient(new Name("David Li"), new Phone("91031282"), new Phone("87438807"), new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), EMPTY_REMARK, new Gender("M"), new Ic("S1111114Z"), new Condition("Unknown"), new BloodType("O+"), EMPTY_APPOINTMENTS, - getTagSet("family")), + getTagSet("priority: MEDIUM")), new Patient(new Name("Irfan Ibrahim"), new Phone("92492021"), new Phone("87438807"), new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_REMARK, new Gender("M"), new Ic("S1111115Z"), new Condition("Unknown"), new BloodType("O+"), - EMPTY_APPOINTMENTS, getTagSet("classmates")), + EMPTY_APPOINTMENTS, getTagSet("priority: LOW")), new Patient(new Name("Roy Balakrishnan"), new Phone("92624417"), new Phone("87438807"), new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_REMARK, new Gender("M"), new Ic("S1111116Z"), new Condition("Unknown"), new BloodType("O+"), - EMPTY_APPOINTMENTS, getTagSet("colleagues")) + EMPTY_APPOINTMENTS, getTagSet("priority: HIGH")) }; } @@ -74,22 +74,30 @@ public static Doctor[] getSampleDoctors() { return new Doctor[] { new Doctor(new Name("Adam Lim"), new Phone("87438000"), new Email("adamlim@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_REMARK, new Gender("M"), - new Ic("S8811111Z"), getAppointmentSet(APPOINTMENT_1), getTagSet("GP")), + new Ic("S8811111Z"), getAppointmentSet(APPOINTMENT_1), getTagSet("SURGEON")), new Doctor(new Name("Bernard Tan"), new Phone("99272000"), new Email("bernardtan@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_REMARK, new Gender("M"), new Ic("S8811112Z"), getAppointmentSet(APPOINTMENT_2, APPOINTMENT_3), - getTagSet("ENTspecialist")) + getTagSet("CARDIOLOGIST")) }; } + public static Appointment[] getSampleAppointments() { + return new Appointment[] {APPOINTMENT_1, APPOINTMENT_2, APPOINTMENT_3}; + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); for (Patient samplePatient : getSamplePatients()) { sampleAb.addPatient(samplePatient); } + for (Doctor sampleDoctor : getSampleDoctors()) { sampleAb.addDoctor(sampleDoctor); } + for (Appointment sampleAppointment : getSampleAppointments()) { + sampleAb.addAppointment(sampleAppointment); + } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java index dbe0e03deb3..816c2936201 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java @@ -1,14 +1,11 @@ package seedu.address.storage; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.AppointmentTime; import seedu.address.model.person.Ic; /** @@ -17,14 +14,11 @@ class JsonAdaptedAppointment { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment's %s field is missing!"; - public static final String INVALID_FIELD_MESSAGE_FORMAT = "Appointment's %s field is invalid!"; public static final String DUPLICATE_PATIENT_AND_DOCTOR_IC = "Appointment's doctor IC and patients IC are the same!"; - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final String doctorIc; private final String patientIc; private final String appointmentTime; - private final String status; /** * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. @@ -32,12 +26,10 @@ class JsonAdaptedAppointment { @JsonCreator public JsonAdaptedAppointment(@JsonProperty("doctorIc") String doctorIc, @JsonProperty("patientIc") String patientIc, - @JsonProperty("appointmentTime") String appointmentTime, - @JsonProperty("status") String status) { + @JsonProperty("appointmentTime") String appointmentTime) { this.doctorIc = doctorIc; this.patientIc = patientIc; this.appointmentTime = appointmentTime; - this.status = status; } /** @@ -46,30 +38,18 @@ public JsonAdaptedAppointment(@JsonProperty("doctorIc") String doctorIc, public JsonAdaptedAppointment(Appointment source) { doctorIc = source.getDoctor().value; patientIc = source.getPatient().value; - appointmentTime = source.getAppointmentTime().format(formatter); - status = source.getStatus(); - } - - public String checkStatus() throws IllegalValueException { - if (status == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Status")); - } - return status; + appointmentTime = source.getAppointmentTime().toString(); } - public LocalDateTime checkAppointmentTime() throws IllegalValueException { + public AppointmentTime checkAppointmentTime() throws IllegalValueException { if (appointmentTime == null) { throw new IllegalValueException( - String.format(MISSING_FIELD_MESSAGE_FORMAT, LocalDateTime.class.getSimpleName())); + String.format(MISSING_FIELD_MESSAGE_FORMAT, AppointmentTime.class.getSimpleName())); } - LocalDateTime result; - try { - result = LocalDateTime.parse(appointmentTime, formatter); - } catch (DateTimeParseException e) { - throw new IllegalValueException(String.format(INVALID_FIELD_MESSAGE_FORMAT, - LocalDateTime.class.getSimpleName())); + if (!AppointmentTime.isValidAppointmentTime(appointmentTime)) { + throw new IllegalValueException(AppointmentTime.MESSAGE_CONSTRAINTS); } - return result; + return new AppointmentTime(appointmentTime); } /** @@ -100,9 +80,8 @@ public Appointment toModelType() throws IllegalValueException { } final Ic modelDoctor = checkIc(doctorIc); final Ic modelPatient = checkIc(patientIc); - final LocalDateTime modelAppointmentTime = checkAppointmentTime(); - final String modelStatus = checkStatus(); - return new Appointment(modelDoctor, modelPatient, modelAppointmentTime, modelStatus); + final AppointmentTime modelAppointmentTime = checkAppointmentTime(); + return new Appointment(modelDoctor, modelPatient, modelAppointmentTime); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java b/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java index 0ff9dd2a57d..4b9ad0b9db3 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedDoctor.java @@ -1,13 +1,24 @@ package seedu.address.storage; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.person.Address; import seedu.address.model.person.Doctor; -import seedu.address.model.person.Person; +import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Ic; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.tag.Tag; /** @@ -30,7 +41,7 @@ public JsonAdaptedDoctor(@JsonProperty("name") String name, @JsonProperty("phone } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Doctor} into this class for Jackson use. */ public JsonAdaptedDoctor(Doctor source) { super(source); @@ -42,10 +53,21 @@ public JsonAdaptedDoctor(Doctor source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Doctor toModelType() throws IllegalValueException { - Person p = super.toModelType(); - - Doctor modelDoctor = new Doctor(p.getName(), p.getPhone(), p.getEmail(), p.getAddress(), p.getRemark(), - p.getGender(), p.getIc(), p.getAppointments(), p.getTags()); - return modelDoctor; + final Name modelName = checkName(); + final Phone modelPhone = checkPhone(); + final Email modelEmail = checkEmail(); + final Address modelAddress = checkAddress(); + final Remark modelRemark = checkRemark(); + final Gender modelGender = checkGender(); + final Ic modelIc = checkIc(); + final List personTags = new ArrayList<>(); + for (JsonAdaptedTag tag : this.getTags()) { + personTags.add(tag.toModelDoctorType()); + } + final Set modelTags = new HashSet<>(personTags); + final List personAppointments = checkAppointments(); + final Set modelAppointments = new HashSet<>(personAppointments); + return new Doctor(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelGender, modelIc, + modelAppointments, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPatient.java b/src/main/java/seedu/address/storage/JsonAdaptedPatient.java index 27cbd3fa325..b8d3739ff45 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPatient.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPatient.java @@ -1,21 +1,31 @@ package seedu.address.storage; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.person.Address; import seedu.address.model.person.BloodType; import seedu.address.model.person.Condition; +import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Ic; +import seedu.address.model.person.Name; import seedu.address.model.person.Patient; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.tag.Tag; /** * Jackson-friendly version of {@link Patient}. */ -class JsonAdaptedPatient extends JsonAdaptedPerson { +public class JsonAdaptedPatient extends JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Patient's %s field is missing!"; @@ -56,17 +66,25 @@ public JsonAdaptedPatient(Patient source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Patient toModelType() throws IllegalValueException { - Person p = super.toModelType(); - + final Name modelName = checkName(); + final Phone modelPhone = checkPhone(); final Phone modelEmergencyContact = checkEmergencyContact(); + final Email modelEmail = checkEmail(); + final Address modelAddress = checkAddress(); + final Remark modelRemark = checkRemark(); + final Gender modelGender = checkGender(); + final Ic modelIc = checkIc(); + final List personTags = new ArrayList<>(); + for (JsonAdaptedTag tag : this.getTags()) { + personTags.add(tag.toModelPatientType()); + } + final Set modelTags = new HashSet<>(personTags); final Condition modelCondition = checkCondition(); final BloodType modelBloodType = checkBloodType(); - - Patient modelPatient = new Patient(p.getName(), p.getPhone(), modelEmergencyContact, p.getEmail(), - p.getAddress(), p.getRemark(), p.getGender(), p.getIc(), modelCondition, modelBloodType, - p.getAppointments(), p.getTags()); - - return modelPatient; + final List personAppointments = checkAppointments(); + final Set modelAppointments = new HashSet<>(personAppointments); + return new Patient(modelName, modelPhone, modelEmergencyContact, modelEmail, modelAddress, modelRemark, + modelGender, modelIc, modelCondition, modelBloodType, modelAppointments, modelTags); } /** diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index 3b148004f3c..2756b3d1969 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -1,9 +1,7 @@ package seedu.address.storage; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; @@ -19,14 +17,18 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.person.Remark; -import seedu.address.model.tag.Tag; + /** * Jackson-friendly version of {@link Person}. */ -class JsonAdaptedPerson { +public abstract class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + public static final String MESSAGE_DUPLICATE_APPOINTMENT_TIME = + "Person has more than 1 appointment at the same timing!"; + public static final String MESSAGE_INVALID_APPOINTMENT = + "Person contains an appointment that does not belong to him!"; private final String name; private final String phone; @@ -83,27 +85,16 @@ public JsonAdaptedPerson(Person source) { } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Gives the list of tags of the Person. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @return a List of JsonAdaptedTags */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); - } - - final Set modelTags = new HashSet<>(personTags); - final Set modelAppointments = new HashSet<>(checkAppointments()); - final Name modelName = checkName(); - final Phone modelPhone = checkPhone(); - final Email modelEmail = checkEmail(); - final Address modelAddress = checkAddress(); - final Remark modelRemark = checkRemark(); - final Gender modelGender = checkGender(); - final Ic modelIc = checkIc(); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelGender, modelIc, - modelAppointments, modelTags); + public List getTags() { + return this.tags; + } + + public List getAppointments() { + return this.appointments; } /** @@ -112,7 +103,7 @@ public Person toModelType() throws IllegalValueException { * @return a valid name object. * @throws IllegalValueException if name is not valid. */ - private Name checkName() throws IllegalValueException { + public Name checkName() throws IllegalValueException { if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -128,7 +119,7 @@ private Name checkName() throws IllegalValueException { * @return a valid phone object. * @throws IllegalValueException if phone is not valid. */ - private Phone checkPhone() throws IllegalValueException { + public Phone checkPhone() throws IllegalValueException { if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } @@ -144,7 +135,7 @@ private Phone checkPhone() throws IllegalValueException { * @return a valid email object. * @throws IllegalValueException if email is not valid. */ - private Email checkEmail() throws IllegalValueException { + public Email checkEmail() throws IllegalValueException { if (email == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); } @@ -160,7 +151,7 @@ private Email checkEmail() throws IllegalValueException { * @return a valid address * @throws IllegalValueException if address is not valid. */ - private Address checkAddress() throws IllegalValueException { + public Address checkAddress() throws IllegalValueException { if (address == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); } @@ -176,7 +167,7 @@ private Address checkAddress() throws IllegalValueException { * @return a valid remark * @throws IllegalValueException if remark is not valid. */ - private Remark checkRemark() throws IllegalValueException { + public Remark checkRemark() throws IllegalValueException { if (remark == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); } @@ -189,7 +180,7 @@ private Remark checkRemark() throws IllegalValueException { * @return a valid gender * @throws IllegalValueException if gender is not valid. */ - private Gender checkGender() throws IllegalValueException { + public Gender checkGender() throws IllegalValueException { if (gender == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName())); } @@ -205,7 +196,7 @@ private Gender checkGender() throws IllegalValueException { * @return a valid ic * @throws IllegalValueException if ic is not valid. */ - private Ic checkIc() throws IllegalValueException { + public Ic checkIc() throws IllegalValueException { if (ic == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Ic.class.getSimpleName())); } @@ -215,7 +206,14 @@ private Ic checkIc() throws IllegalValueException { return new Ic(ic); } - private List checkAppointments() throws IllegalValueException { + /** + * Gives the list of appointments of the Person. + * Please ensure that this method is called only after the Ic has been added to the person. + * This checks for any appointments happening at the same time and any appointments not belonging to this person. + * + * @return a List of JsonAdaptedAppointments + */ + public List checkAppointments() throws IllegalValueException { final List listOfAppointments = new ArrayList<>(); for (JsonAdaptedAppointment appointment : appointments) { listOfAppointments.add(appointment.toModelType()); diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..da63271c38d 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -38,9 +38,16 @@ public String getTagName() { * * @throws IllegalValueException if there were any data constraints violated in the adapted tag. */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); + public Tag toModelPatientType() throws IllegalValueException { + if (!Tag.isValidFullPatientTagName(tagName)) { + throw new IllegalValueException(Tag.INVALID_PATIENT_TAG); + } + return new Tag(tagName); + } + + public Tag toModelDoctorType() throws IllegalValueException { + if (!Tag.isValidDoctorTagName(tagName)) { + throw new IllegalValueException(Tag.INVALID_DOCTOR_TAG); } return new Tag(tagName); } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index df089a0a0db..ba333e8bab1 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,6 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; import seedu.address.model.person.Patient; @@ -21,18 +22,22 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_DOCTOR = "Doctors list contains duplicate doctor(s)."; public static final String MESSAGE_DUPLICATE_PATIENT = "Patients list contains duplicate patient(s)."; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "Appointments list contains duplicate appointments"; private final List doctors = new ArrayList<>(); private final List patients = new ArrayList<>(); + private final List appointments = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator public JsonSerializableAddressBook(@JsonProperty("patients") List patients, - @JsonProperty("doctors") List doctors) { + @JsonProperty("doctors") List doctors, + @JsonProperty("appointments") List appointments) { this.doctors.addAll(doctors); this.patients.addAll(patients); + this.appointments.addAll(appointments); } /** @@ -43,6 +48,8 @@ public JsonSerializableAddressBook(@JsonProperty("patients") List { + + private static final String FXML = "AppointmentListCard.fxml"; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + public final Appointment appointment; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label patientIc; + @FXML + private Label doctorIc; + @FXML + private Label appointmentTime; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public AppointmentCard(Appointment appointment, int displayedIndex) { + super(FXML); + this.appointment = appointment; + id.setText("APPOINTMENT " + displayedIndex); + patientIc.setText("Patient IC: " + this.appointment.getPatient().toString()); + doctorIc.setText("Doctor IC: " + this.appointment.getDoctor().toString()); + appointmentTime.setText("Time of appointment: " + this.appointment.getAppointmentTime().toString()); + } +} diff --git a/src/main/java/seedu/address/ui/AppointmentListPanel.java b/src/main/java/seedu/address/ui/AppointmentListPanel.java new file mode 100644 index 00000000000..0399c8d346f --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.appointment.Appointment; + +/** + * Panel containing the list of persons. + */ +public class AppointmentListPanel extends UiPart { + private static final String FXML = "AppointmentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(AppointmentListPanel.class); + + @FXML + private ListView appointmentListView; + + /** + * Creates a {@code AppointmentListPanel} with the given {@code ObservableList}. + */ + public AppointmentListPanel(ObservableList appointmentList) { + super(FXML); + appointmentListView.setItems(appointmentList); + appointmentListView.setCellFactory(listView -> new AppointmentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Patient} using a {@code PatientCard}. + */ + class AppointmentListViewCell extends ListCell { + @Override + protected void updateItem(Appointment appointment, boolean empty) { + super.updateItem(appointment, empty); + + if (empty || appointment == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new AppointmentCard(appointment, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/DoctorCard.java b/src/main/java/seedu/address/ui/DoctorCard.java index a1adde8421a..f876a7e479f 100644 --- a/src/main/java/seedu/address/ui/DoctorCard.java +++ b/src/main/java/seedu/address/ui/DoctorCard.java @@ -7,7 +7,6 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Doctor; @@ -42,8 +41,6 @@ public class DoctorCard extends UiPart { @FXML private FlowPane tags; @FXML - private FlowPane appointments; - @FXML private Label remark; @FXML private Label gender; @@ -67,10 +64,5 @@ public DoctorCard(Doctor doctor, int displayedIndex) { doctor.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - doctor.getAppointments().stream() - .sorted(Comparator.comparing(Appointment::getAppointmentTime)) - .forEach(appointment -> appointments.getChildren() - .add(new Label("Appointment with Patient: " + appointment.getPatient() + " at " - + appointment.getAppointmentTime().format(Appointment.FORMATTER)))); } } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..fc100dc5c9d 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-t09-3.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); @@ -46,6 +46,7 @@ public HelpWindow() { /** * Shows the help window. + * * @throws IllegalStateException *

    *
  • diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 2aa4dd264a3..ce53afcc6e9 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -33,6 +33,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private DoctorListPanel doctorListPanel; private PatientListPanel patientListPanel; + private AppointmentListPanel appointmentListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -46,6 +47,8 @@ public class MainWindow extends UiPart { private StackPane doctorListPanelPlaceholder; @FXML private StackPane patientListPanelPlaceholder; + @FXML + private StackPane appointmentListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -118,7 +121,8 @@ void fillInnerParts() { patientListPanelPlaceholder.getChildren().add(patientListPanel.getRoot()); doctorListPanel = new DoctorListPanel(logic.getFilteredDoctorList()); doctorListPanelPlaceholder.getChildren().add(doctorListPanel.getRoot()); - + appointmentListPanel = new AppointmentListPanel(logic.getFilteredAppointmentList()); + appointmentListPanelPlaceholder.getChildren().add(appointmentListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); diff --git a/src/main/java/seedu/address/ui/PatientCard.java b/src/main/java/seedu/address/ui/PatientCard.java index 9bf55822534..7309c387bcc 100644 --- a/src/main/java/seedu/address/ui/PatientCard.java +++ b/src/main/java/seedu/address/ui/PatientCard.java @@ -7,7 +7,6 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Patient; /** @@ -42,8 +41,6 @@ public class PatientCard extends UiPart { @FXML private FlowPane tags; @FXML - private FlowPane appointments; - @FXML private Label remark; @FXML private Label gender; @@ -73,12 +70,6 @@ public PatientCard(Patient person, int displayedIndex) { person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - person.getAppointments().stream() - .sorted(Comparator.comparing(Appointment::getAppointmentTime)) - .forEach(appointment -> appointments.getChildren() - .add(new Label("Appointment with Doctor: " + appointment.getDoctor() + " at " - + appointment.getAppointmentTime().format(Appointment.FORMATTER)))); - condition.setText("Condition: " + person.getCondition().value); bloodType.setText("Blood Type: " + person.getBloodType().value); emergencyContact.setText("Emergency Contact: " + person.getEmergencyContact().value); diff --git a/src/main/resources/view/AppointmentListCard.fxml b/src/main/resources/view/AppointmentListCard.fxml new file mode 100644 index 00000000000..78dca91284c --- /dev/null +++ b/src/main/resources/view/AppointmentListCard.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/AppointmentListPanel.fxml b/src/main/resources/view/AppointmentListPanel.fxml new file mode 100644 index 00000000000..35b3d93ce99 --- /dev/null +++ b/src/main/resources/view/AppointmentListPanel.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index dab8ae35182..4e06ce35733 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,39 +1,39 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: #f0f0f0; /* Light gray background */ + background-color: #e6e6e6; /* Slightly darker gray */ } .label { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #333333; /* Dark gray text */ -fx-opacity: 0.9; } .label-bright { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #0088cc; /* Blue text */ -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #0088cc; /* Blue text */ -fx-opacity: 1; } .label-list-header { -fx-font-size: 24pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #0088cc; /* Blue text */ -fx-opacity: 1; } .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Arial", Helvetica, sans-serif; } .tab-pane { @@ -47,9 +47,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: #f0f0f0; /* Light gray background */ + -fx-control-inner-background: #f0f0f0; /* Light gray background */ + -fx-background-color: #f0f0f0; /* Light gray background */ -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -66,38 +66,38 @@ -fx-border-color: transparent transparent - derive(-fx-base, 80%) + #0088cc transparent; -fx-border-insets: 0 10 1 0; } .table-view .column-header .label { -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #0088cc; /* Blue text */ -fx-alignment: center-left; -fx-opacity: 1; } .table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; + -fx-background-color: #0088cc; /* Blue background */ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-background-color: #f0f0f0; /* Light gray background */ + -fx-border-color: transparent transparent transparent #0088cc; /* Blue border */ } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #f0f0f0; /* Light gray background */ } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #f0f0f0; /* Light gray background */ } .list-cell { @@ -107,63 +107,63 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #f0f0f0; /* Light gray background */ } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #ADD8E6; /* Blue background */ } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: #0088cc; /* Blue border */ -fx-border-width: 1; } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: #333333; /* Dark gray text */ } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Arial", Helvetica, sans-serif; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: #333333; /* Dark gray text */ } .cell_small_label { - -fx-font-family: "Segoe UI"; + -fx-font-family: "Arial", Helvetica, sans-serif; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #333333; /* Dark gray text */ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #f0f0f0; /* Light gray background */ } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: #f0f0f0; /* Light gray background */ + -fx-border-color: #0088cc; /* Blue border */ -fx-border-top-width: 1px; } .pane-with-white-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: white; + -fx-background-color: #f0f0f0; /* Light gray background */ + -fx-border-color: white; /* White border */ -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Arial", Helvetica, sans-serif; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #0088cc; /* Blue text */ } .result-display .label { @@ -171,79 +171,74 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #333333; /* Dark gray text */ -fx-padding: 4px; -fx-pref-height: 30px; } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ + -fx-border-color: #d8d8d8; /* Light gray border */ -fx-border-width: 1px; } .status-bar-with-border .label { - -fx-text-fill: white; + -fx-text-fill: #333333; /* Dark gray text */ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ + -fx-border-color: #d8d8d8; /* Light gray border */ -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #f0f0f0; /* Light gray background */ } .context-menu .label { - -fx-text-fill: white; + -fx-text-fill: #333333; /* Dark gray text */ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #f0f0f0; /* Light gray background */ } .menu-bar .label { -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: "Arial", Helvetica, sans-serif; + -fx-text-fill: #0088cc; /* Blue text */ -fx-opacity: 0.9; } .menu .left-container { - -fx-background-color: black; + -fx-background-color: white; /* White background */ } -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ .button { -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; + -fx-border-color: #0088cc; /* Blue border */ -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-background-color: #f0f0f0; /* Light gray background */ + -fx-font-family: "Arial", Helvetica, sans-serif; -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; + -fx-text-fill: #0088cc; /* Blue text */ -fx-background-insets: 0 0 0 0, 0, 1, 2; } .button:hover { - -fx-background-color: #3a3a3a; + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .button:pressed, .button:default:hover:pressed { -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-text-fill: #f0f0f0; /* Light gray text */ } .button:focused { @@ -256,35 +251,35 @@ .button:disabled, .button:default:disabled { -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; + -fx-background-color: #f0f0f0; /* Light gray background */ + -fx-text-fill: #333333; /* Dark gray text */ } .button:default { - -fx-background-color: -fx-focus-color; + -fx-background-color: #0088cc; /* Blue background */ -fx-text-fill: #ffffff; } .button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); + -fx-background-color: #0066aa; /* Darker blue background */ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: #f0f0f0; /* Light gray background */ } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: #f0f0f0; /* Light gray background */ } .dialog-pane > *.label.content { -fx-font-size: 14px; -fx-font-weight: bold; - -fx-text-fill: white; + -fx-text-fill: #333333; /* Dark gray text */ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .dialog-pane:header *.header-panel *.label { @@ -295,11 +290,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #e6e6e6; /* Slightly darker gray background */ } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #0088cc; /* Blue thumb */ -fx-background-insets: 3; } @@ -327,18 +322,18 @@ #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #0088cc; /* Blue text */ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: transparent #e6e6e6 transparent #e6e6e6; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: #e6e6e6 #e6e6e6 #0088cc #e6e6e6; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Arial", Helvetica, sans-serif; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #333333; /* Dark gray text */ } #filterField, #personListPanel, #personWebpage { @@ -346,7 +341,7 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: transparent, #e6e6e6, transparent, #e6e6e6; -fx-background-radius: 0; } @@ -357,7 +352,7 @@ #tags .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #009D94; /* Blue background */ -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; diff --git a/src/main/resources/view/DoctorListCard.fxml b/src/main/resources/view/DoctorListCard.fxml index 30e1ecbac7a..8c6484818e0 100644 --- a/src/main/resources/view/DoctorListCard.fxml +++ b/src/main/resources/view/DoctorListCard.fxml @@ -24,7 +24,7 @@ -