diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..9bdba7ffcb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,7 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+
+# VSCode files
+.vscode/launch.json
+.vscode/settings.json
diff --git a/README.md b/README.md
index 13f5c77403f..39928c18a61 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,21 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+[](https://github.com/AY2324S2-CS2103-F09-3/tp/actions)

-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+**TutorRec** is a desktop application for tutors to manage client contacts.
+It is
+- CLI based
+- with a simple GUI
+- and super-fast to use.
+
+### Links
+- [Project Website](https://ay2324s2-cs2103-f09-3.github.io/tp/)
+- [User Guide](https://ay2324s2-cs2103-f09-3.github.io/tp/UserGuide.html)
+- [Developer Guide](https://ay2324s2-cs2103-f09-3.github.io/tp/DeveloperGuide.html)
+- [About Us](https://ay2324s2-cs2103-f09-3.github.io/tp/AboutUs.html)
+
+### Acknowledgements
+- This project is **a part of the se-education.org initiative**. If you would
+like to contribute code to this project, see
+[se-education.org](https://se-education.org/#https://se-education.org/#contributing) for more
+info.
diff --git a/build.gradle b/build.gradle
index a2951cc709e..41ea15bcd3c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,11 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'tutorrec.jar'
}
defaultTasks 'clean', 'test'
+
+run {
+ enableAssertions = true
+}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..35280ae55bf 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Chin Zhe Ning
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/biinnnggggg)]
-* Role: Project Advisor
+* Roles: Documentation, In charge of `Logic`
+* Responsibilities: looks after quality of various project documents, diagrams and the `Logic` component
+### Jonathan Chong
-### Jane Doe
+
-
+[[github](http://github.com/jonchong98)]
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+* Roles: User Interface design and implementation
+* Responsibilities: creating UI mockups and implementing design into product
-* Role: Team Lead
-* Responsibilities: UI
-### Johnny Doe
+### Darylgolden
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/darylgolden)]
-* Role: Developer
-* Responsibilities: Data
+* Roles: Git expert, Testing, Integration
+* Responsibilities: helps other team member with Git matters, ensures
+ the testing of the project is timely and done properly, In charge of
+ versioning of the code, maintaining the code repository, integrating
+ various parts of the software to create a whole.
-### Jean Doe
+### Aidan Goh
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/TopKec)]
-* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Role: Code Quality, In charge of `Model` package
+* Responsibilities: looks after code quality, ensures adherence to coding
+standards, etc., looks after quality of `Model` package
-### James Doe
+### Tan Qin Yong
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/tanqinyong)]
-* Role: Developer
-* Responsibilities: UI
+* Role: Scheduling and Tracking
+* Responsibilities: in charge of defining, assigning and tracking of tasks
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 1b56bb5d31b..c60351a935b 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -5,11 +5,12 @@ title: Developer Guide
* Table of Contents
{:toc}
+
--------------------------------------------------------------------------------------------------------------------
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* Existing AB3 code structure and implementation was referenced for new TutorRec features.
--------------------------------------------------------------------------------------------------------------------
@@ -18,6 +19,7 @@ title: Developer Guide
Refer to the guide [_Setting up and getting started_](SettingUp.md).
--------------------------------------------------------------------------------------------------------------------
+
## **Design**
@@ -74,6 +76,10 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se-
The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+`PersonCard` strictly represents a UI element depicting a `Person` object as found in the `Model` component.
+
+`ResultDisplay` is used to display any kind of output generated by commands. This includes command execution success/fail messages, command execution results and help messages. Command execution results vary depending on the command executed.
+
The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
@@ -83,6 +89,8 @@ The `UI` component,
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+
+
### Logic component
**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
@@ -114,6 +122,8 @@ How the parsing works:
* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
+
+
### Model component
**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
@@ -122,17 +132,12 @@ How the parsing works:
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
+* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object)
+and all associated `Appointment` objects (which are contained in a `DisjointAppointmentList`).
* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
-
-
-
-
-
### Storage component
@@ -155,6 +160,76 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
+### Managing appointments
+
+#### Implementation
+
+A core feature of TutorRec is the ability to add appointments to a contact.
+Appointments are added with the field (`/ap`) when doing an `add` or `edit` command, for instance:
+
+- `add n/John Doe /ap 12:00-13:00 MON` will add a person with the name "John Doe"
+and an appointment on Monday from 12:00 to 13:00 to the contact list.
+
+The following diagram describes the process of adding a valid Person appointment with the `add` command:
+
+
+
+The appointments, after being parsed, are stored in a list of appointments within `Person`.
+This is implemented as an `AppointmentList` field.
+To prevent appointment overlap, we check both
+1. `Appointment` overlap within `Person`'s `AppointmentList` and
+2. `Appointment` overlap between the appointments in `AppointmentList` and the existing appointments in `Model`.
+
+
+
+
+Note that certain details, such as other fields in a `Person` have been omitted for brevity.
+
+
+**Design Considerations**
+
+All existing appointments are stored in `Model` in a `DisjointAppointmentList`. We chose to make this distinction between
+`AppointmentList` and `DisjointAppointmentList` to allow for easy utility of `AppointmentList` in storing and working with
+parsed appointments.
+
+
+
+### Notes for students
+#### Implementation
+
+TutorRec is able to add notes to each student. They are added as a field (`/nt`) when doing an `add` or `edit` command, so something similar to:
+
+`edit 1 /nt "This student is very good at math, but struggles with English."` will edit the person on index 1 to have the note "This student is very good at math, but struggles with English."
+
+### Duplicate contacts
+#### Implementation
+
+In TutorRec, contacts are uniquely identified by their names. No two contacts can have the exact same name, ensuring that duplicate contacts are not created.
+
+**Case Insensitivity**: Contact names in TutorRec are not case-sensitive. For example, 'John Doe' and 'JOhn dOE' are treated as the same name.
+
+**Whitespace Sensitivity**: Unlike case sensitivity, whitespaces in names do affect differentiation. Thus, 'Mary Anne' and 'Maryanne' are recognized as distinct names due to the difference in whitespace.
+
+**Handling Potential Duplicates**: Whenever a user attempts to add or edit a contact, TutorRec checks for names that might be similar by ignoring differences in case or whitespace. If a potential duplicate is detected, the user is warned when the contact is added.
+
+**Contact Information Flexibility**: Unlike names, a contact's phone number and email address do not have to be unique in TutorRec. This allows for scenarios where a single contact detail, such as a phone number or email, might be associated with multiple contacts, such as a parent with several children enrolled. This design decision facilitates easier management of family-related records, ensuring that it is permissible for different contacts to share identical contact information.
+
+
+
+### Listing Students
+
+#### Implementation
+
+TutorRec is also able to list all current students in the address book. Note that the command `list` does not modify the address book. Additionally, it does not take in any extra parameters. It can be simply called as follows:
+
+- `list` will show all current students in the address book in the `PersonListPanel`
+
+The example shown below will describe the process for listing all students during the `list` command.
+
+
+
+
+
### \[Proposed\] Undo/redo feature
#### Proposed Implementation
@@ -220,6 +295,8 @@ Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Sinc

+
+
The following activity diagram summarizes what happens when a user executes a new command:
@@ -237,13 +314,6 @@ The following activity diagram summarizes what happens when a user executes a ne
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
-
-### \[Proposed\] Data archiving
-
-_{Explain here how the data archiving feature will be implemented}_
-
-
--------------------------------------------------------------------------------------------------------------------
## **Documentation, logging, testing, configuration, dev-ops**
@@ -262,13 +332,14 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
+* busy home tutor for primary school students
+* has a need to manage a large number of students
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: provides easy access to client info and organizes it in an efficient and readable way for day-to-day use, optimized for tutors that prefer CLI.
### User stories
@@ -277,51 +348,313 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
| Priority | As a … | I want to … | So that I can… |
| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| `* *` | new home tutor | have adding information be intuitive and logical | use the app without hassle |
+| `* *` | new user | have a quick start guide | learn how to use the app |
+| `* *` | new home tutor | follow a set syllabus for my clients | |
+| `* *` | new home tutor | be able to quickly create a student’s profile with their relevant information | |
+| `* *` | new home tutor | track the performance of my students | prove to their parents that they are improving and my effectiveness as a tutor |
+| `* * *` | new home tutor | track all my appointments | reduce the chance I forget or double book myself |
+| `* *` | new home tutor | keep track of my finances–such as which client has paid me for my work over a month | reduce my stress when it comes to keeping track of who has paid me for my work, and so forth. |
+| `* *` | busy home tutor | seamlessly create new tasks | quickly and efficiently set up my daily routine |
+| `* *` | busy home tutor | be reminded about my upcoming appointments for the day | not accidentally forget |
+| `* *` | returning home tutor | re-implementation of more information in a large amount to be easy | add information en masse without stress |
+| `* *` | home tutor with clients who are nearing exam season | properly ensure that they are improving as planned, and also allow them to set benchmarks that I can remember | |
+| `* *` | busy home tutor | block certain times out for lunch and dinner | not accidentally overwrite those times with an additional client |
+| `* * *` | experienced home tutor | delete students I am no longer teaching on TutorRec | unclutter my interface |
+| `* * *` | experienced home tutor | update student information and details | ensure accurate records are maintained |
+| `* * *` | passionate home tutor | TutorRec to keep notes for each student | tailor my teaching style accordingly |
+| `* *` | experienced home tutor with many students | ability to categorise students by skill level, subject or group (p1, p2, p3…) | quickly locate their information when needed |
+| `* *` | experienced home tutor | flexibility to choose what to teach my students (custom lessons) | personalise each lesson for different students |
+| `* *` | experienced home tutor | easily identify students with weak performance | focus on weaker students |
+| `* *` | experienced home tutor | view a student’s records for the length of time I have tutored them | |
+| `* *` | experienced home tutor | get some insights and analytics on a student’s performance over time | further refine my teaching methods accordingly |
+| `* *` | busy home tutor | quickly reschedule my appointments with my clients | fit my ever-changing schedule, preventing a large amount of hassle |
+| `* *` | tutor who just received a new wave of clients | separate my old and new clients | keep the interface orderly and easy-to-follow |
+| `* * *` | busy home tutor | make quick notes about my students | keep track of information specific to each client |
+| `* *` | assignment-ridden home tutor | note cancellations in my schedule due to increased workload from my end | my schedule is accurate to reality |
+| `* *` | wary home tutor | backup and import data | safeguard myself against potential data corruption and/or physical destruction of my devices |
+| `* *` | home tutor with a new device | quickly transfer data from one device to another | prevent the pain of having to input previous data manually |
+| `* *` | online tutor | organize sessions with students in a different country and automatically convert dates and times to my time zone | |
+| `* *` | online home tutor | keep Zoom links with sessions and other information about the student in one place | |
+| `* *` | online home tutor with students who are abroad | convert time at a glance | be on time for my student’s lessons |
+| `* * *` | home tutor who just moved abroad | remove previous clients I cannot tutor due to the distance gap | keep my schedule clean |
+| `* *` | home tutor who just moved abroad | ways of tagging my students | keep track of different needs arising due to cultural differences |
+
+
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `TutorRec` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case: Delete a student**
+
+**MSS**
+
+1. User requests to list students
+2. TutorRec shows a list of students
+3. User requests to delete a specific student in the list
+4. TutorRec deletes the student
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
-**Use case: Delete a person**
+
+**Use case: Viewing appointments**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to view current appointments for the day
+2. TutorRec shows a list of appointments for the day
Use case ends.
**Extensions**
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 1b. User inputs an invalid day format as an input.
+
+ * 1b1. TutorRec shows an error message.
+
+ Use case ends.
+
+
+**Use case: Sorting students**
+
+**MSS**
+
+1. User requests to view students of a particular category
+2. TutorRec shows a filtered list containing only students with this category
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 1b. The given category does not have any students assigned to it.
+
+ * 1b1. TutorRec shows an empty list.
+
+ Use case ends.
+
+* 1c. User inputs an invalid category.
+
+ * 1c1. TutorRec shows an error message.
+
+ Use case ends.
+
+
+**Use case: Editing a student's details**
+
+**MSS**
+
+1. User requests to list students
+2. TutorRec displays a list of students
+3. User requests to edit the details of a specific student in the list
+4. TutorRec updates the details of this student
+5. TutorRec displays the updated information of this student
+
+ Use case ends.
+
+**Extensions**
+
* 2a. The list is empty.
Use case ends.
* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+ * 3a1. TutorRec shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+* 3b. User inputs a field that does not exist (e.g. adding a nonexistent /q field).
+
+ * 3b1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+
+**Use case: Finding a student**
+
+**MSS**
+
+1. User requests to list all students with a particular name
+2. TutorRec displays a reduced list containing all students that meet the criteria of the name requested
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The list is empty.
+
+ Use case ends.
+
+* 1b. No students exist with the given name.
+
+ * 1b1. TutorRec displays an empty list.
+
+ Use case ends.
+
+**Use case: Checking improvements of a student**
+
+**MSS**
+
+1. User requests to list students
+2. TutorRec displays a list of students
+3. User updates a specific student's grades for a given test
+4. TutorRec updates the grades for this student
+5. TutorRec displays that the student's grades has been updated
+6. User requests to view a list of a specific student's grades
+7. TutorRec displays a history of this student's grades
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. An invalid score is listed as the input.
+
+ * 3b1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 6a. The given index is invalid.
+
+ * 6a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 6b. The student has no grades saved.
+
+ * 6b1. TutorRec displays nothing.
+
+ Use case resumes at step 2.
+
+**Use case: Updating payment status**
+
+**MSS**
+
+1. User requests to list students
+2. TutorRec displays a list of students
+3. User chooses to mark a specific student as having made their payment
+4. TutorRec updates the payment status of this student to be complete
+5. User chooses to mark a specific as not having made their payment
+6. TutorRec updates the payment status of this student to be incomplete
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The student selected already has had their payment marked as made.
+
+ * 3b1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 5a. The given index is invalid.
+
+ * 5a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 5b. The student selected already has had their payment marked as made.
+
+ * 5b1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+
+**Use case: Creating an appointment**
+
+**MSS**
+
+1. User requests to list students
+2. TutorRec displays a list of students
+3. User sets a specific student to have an appointment at a particular time and date
+4. TutorRec updates details about this student
+5. TutorRec displays details of appointment to user
+
+ End of use case.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. Insufficient information is given to make an appointment.
+
+ * 3b1. TutorRec shows an error message.
+
+ Use case resumes at step 2.
+
+* 3c. The time and date inputted by the user clashes with an existing appointment previously made by the user.
+
+ * 3c1. TutorRec shows an error message.
+ * 3c2. TutorRec displays information of student which has an appointment that resulted in the timing clash, and the date and time of this appointment.
+
+ Use case resumes at step 2.
+
+
### Non-Functional Requirements
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
+1. TutorRec should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. TutorRec should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-
-*{More to be added}*
+4. TutorRec is not required to handle multiple users (i.e. multi-user product); it is a single-user product.
+5. TutorRec needs to be developed in a breadth-first incremental manner, with weekly updates.
+6. TutorRec's data should be stored locally and should be in a human editable text file.
+7. TutorRec should not use a DBMS to store data.
+8. TutorRec should work without requiring an installer.
+9. TutorRec should be packaged into a single JAR file for releases.
+10. TutorRec's JAR files should not exceed 100MB.
+11. The development of TutorRec should follow the Object-oriented paradigm.
+12. TutorRec should be able to respond within three seconds.
+13. TutorRec should be designed to function offline.
+14. TutorRec should not require additional hardware beyond standard computing devices (e.g., desktops, laptops, tablets) commonly availabe to users.
### Glossary
@@ -329,6 +662,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
* **Private contact detail**: A contact detail that is not meant to be shared with others
--------------------------------------------------------------------------------------------------------------------
+
## **Appendix: Instructions for manual testing**
@@ -345,16 +679,19 @@ testers are expected to do more *exploratory* testing.
1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Open a terminal inside the folder containing the jar file and run `java -jar tutorrec.jar` Expected: Shows the GUI with a set of sample persons. The window size may not be optimum.
1. Saving window preferences
1. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by running `java -jar tutorrec.jar` in the folder containing the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+1. Shutting down
+
+ 1. Use the `exit` command or click on the close window button on the title bar
+ of TutorRec.
### Deleting a person
@@ -371,12 +708,49 @@ testers are expected to do more *exploratory* testing.
1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+### Viewing all appointments / appointments on a specific day
+
+1. Viewing all appointments
+ 1. Prerequisites: TutorRec is running and contains persons with appointments. Each day of the week has at least 1 appointment belonging to any person. The current displayed list of persons is displaying all persons.
+ 2. Test case: `appointments`
+ Expected: All appointments of the persons currently displayed in the list are displayed.
+2. Viewing appointments on a specific day
+ 1. Prerequisites: TutorRec is running and contains persons with appointments. There is at least one appointment scheduled on Monday belonging to any person in the current displayed list.
+ 2. Test case: `appointments MON`
+ Expected: Appointments scheduled on Monday of the persons current displayed in the list are displayed.
+
+### Viewing details of a person
+
+1. Viewing details of a person currently displayed in the list
+ 1. Prerequisites: There is at least one person in the current displayed list.
+ 2. Run `view 1`.
+ Expected: All details of the person are displayed in the window on the right.
### Saving data
-1. Dealing with missing/corrupted data files
+1. Dealing with missing data file
+
+ 1. Open a terminal and run `java -jar tutorrec.jar` in the folder containing the jar file. This will cause TutorRec to generate a data folder containing addressbook.json which stores all person data.
+ 2. Exit TutorRec.
+ 3. Delete the addressbook.json file in the data folder.
+ 4. Run `java -jar tutorrec.jar` in the terminal again. Expected: TutorRec regenerates the data file containing some sample persons.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+--------------------------------------------------------------------------------------------------------------------
-1. _{ more test cases … }_
+## **Appendix: Planned Enhancements**
+
+Team Size: 5
+
+1. **Standardize display of person's contact details with other fields when missing information:** Currently when a person is missing contact information such as phone number, email and address, the view command shows '---' instead of just '-' like other fields when there's no information. This will be changed in a future version to show the same '-' as other fields when information is missing.
+2. **Improve UI to handle extremely long tags going out of UI bounds:** Currently when a tag contains a string that is extremely long, it will stretch beyond the boundary of the person list. This will be improved in a future version to allow the tag to wrap around to display the full tag.
+3. **Set a limit to the length of a name:** Currently TutorRec is capable of accepting a name of any length. This causes issues when the name entered is excessively long, resulting in it being not displayed properly in the UI. Setting a limit on the length of a name will prevent it from being cut-off in the UI.
+4. **Fix the `find` command to be reflective of the current list:** Currently, the `find` command properly filters the user list when the command is typed, but does not respond to `delete` commands properly. For example, if a person "David" is searched for in the list, and `find David` is used, then it currently will properly display a filtered list with "David" in it. Suppose David has index 1.
+ - If `delete 1` is used, the filtered list will incorrectly still list David as still being in the list of contacts.
+ - If `edit 1 [some edits]` is used, the filtered list will incorrectly completely reset to show the entire full list, with or without David.
+ - In either case, any command to modify the list should properly interact with the previous `find` command, so it should remove David as part of the contact list if `delete` is used, or continue showing the filtered list if `edit` is used, and so forth.
+5. **Improve error message handling:** Currently, there are certain user-inputted errors which are not properly reflected in TutorRec. For example, typing `edit 1 n/` is properly identified as an invalid command (as a person **must not** have their name removed), but the feedback given by TutorRec is that names should only be alphanumeric, and should not be blank - which is technically correct, but does not communicate sufficiently with the user that the `Name` field cannot be removed in `edit`.
+6. **Improve appointment conflict detection:** Currently, TutorRec disallows conflicting appointments, that is to say, appointments that overlap at a particular time on a particular day. This however, does not account for special cases where the timing of an appointment is different for two days but on the same day, (For example, two appointments, one on the current Sunday, and one on the next Sunday, the former from 1400-1700 and the latter at 1500-1800 is marked as a conflict.) and so an improvement to TutorRec's way of detecting conflicting appointments could improve user experience.
+7. **Allow special characters as part of names:** Currently, TutorRec strictly allows **only** alphanumeric characters to be part of a name. However, this does not account for people with special characters as part of their legal names, such as the use of `s/o` and `-`. This improvement would allow for accurate recording of names.
+8. **Improve consistency of multiple prefixes:** Currently, TutorRec accepts multiple arguments for certain prefixes. For example, users can have multiple prefixes for `subject`, `edit 4 s/ENGLISH s/MATH` resulting in the student having both subjects. However, for the `note` and `level` prefixes, TutorRec allows multiple prefixes but only takes the last argument. `edit 4 l/p1 l/p6` results in the student having only the `p6` level assigned. To improve on this, we can enhance the consistency of accepting multiple prefixes across all commands in the project.
+9. **Enable resizing of TutorRec:** Currently, TutorRec is not resizable as a window. This change would improve user experience.
+10. **Improve duplicate name detection in TutorRec:** Presently, TutorRec permits additional white spaces between names, such as **'John Doe'**, which is distinguished as a separate name from **'John Doe'**. We can refine duplicate name detection by devising a more robust algorithm to address this issue and efficiently identify near matches.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 7abd1984218..92821758284 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,7 +3,7 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+TutorRec is a **desktop app for 1-to-1 primary school home tutors to manage student contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TutorRec can get your contact management tasks done faster than traditional GUI apps.
* Table of Contents
{:toc}
@@ -14,20 +14,20 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+1. Download the latest `tutorrec.jar` from [here](https://github.com/AY2324S2-CS2103-F09-3/tp/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Copy the file to the folder you want to use as the _home folder_ for TutorRec.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar tutorrec.jar` command to run the application.
+ A GUI similar to the one below should appear in a few seconds. Note how the app contains some sample data.

-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+1. Type a command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try:
* `list` : Lists all contacts.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * `add n/Jun Jie p/98765432 e/jj@example.com a/Clementi Ave 3, block 442, #06-01 nt/Weak at Maths t/referral ap/10:00-12:00 SAT s/MATH l/P1` : Adds a contact named `Jun Jie` to the address book.
* `delete 3` : Deletes the 3rd contact shown in the current list.
@@ -38,6 +38,7 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
1. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
+
## Features
@@ -46,13 +47,13 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
**:information_source: Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+ e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/Jun Jie`.
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ e.g `n/NAME [t/TAG]` can be used as `n/Jun Jie t/` or as `n/Jun Jie`.
* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/Raffles`, `t/Raffles t/ALevel` etc.
* Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
@@ -60,12 +61,95 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* Prefixes are adjusted to accept predefined convenient short forms,
+ e.g., `hp/`, `addr/`, `subj/`, `lvl/`.
+
+ * You may choose to use short form or long form of prefixes, e.g., `n/` or `name/`, which are interchangeable.
+
+ * Prefixes are also adjusted to accept some predefined slightly incorrect variations, in case of user typos.
+ The full list of accepted typos and short forms are listed below:
+ * `n/`: `name/` `nae/` `nam/`
+ * `p/`: `phone/` `phon/` `hp/` `handphone/`
+ * `e/`: `email/` `emai/` `em/` `ema/`
+ * `a/`: `address/` `addr/` `add/` `ad/` `addres/` `adress/`
+ * `p/`: `phone/` `phon/` `hp/` `handphone/`
+ * `nt/`: `note/` `not/` `nt/`
+ * `t/`: `tag/` `ta/` `tg/`
+ * `ap/`: `appointment/` `appt/` `appoint/` `appointmen/`
+ * `s/`: `subject/` `subj/` `sub/` `subjec/` `subje/`
+ * `l/`: `level/` `lvl/` `leve/` `lv/` `lev/` `lvel/` `evel/`
+
+* TutorRec is currently **not** resizable
+
* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+
+
+### Formatting fields for a person
+
+A person has the following fields: `Name`, `Phone`, `Email`, `Address`, `Note`, `Tag`, `Appointment`,
+`Subject` and `Level`.
+
+Below lists the requirements for each to be a valid field.
+
+- `Name`: Must be alphanumeric.
+ - `Jane`, `Jane1` are valid
+ - `Jane@`, `**&&&` are not valid.
+- `Phone`: Must only contain numbers
+ - `999`, `12341234` are valid.
+ - `123Phone`, `aeiou` are not valid.
+- `Email`: Contains two parts, in the format `local-part@domain`
+ - `local-part` must adhere to the following restrictions:
+ - Contain only alphanumeric characters
+ - May contain the following special characters `+_.-`
+ - May not begin with the above mentioned special characters
+ - `domain` must adhere to the following restrictions:
+ - contain only letters, numbers, and dashes (`-`), note that hyphens **cannot** be the first or last characters of the domain
+ - the final part of the domain:
+ - is defined by a `.` to separate it from other parts of the domain
+ - is defined by the entire domain if no `.` is present
+ - must be at least two characters long
+ - ergo, the following are examples of domains which are valid and invalid:
+ - `cc`, `test.com`, `name-separator.gov` are valid
+ - `a`, `t*.ab`, `invalid.sep-` are not valid
+ - With these restrictions in mind, the following are some valid and invalid emails:
+ - `alex@example.com`, `jorge@website.site.com`, `jack_jane.john@example.com` are valid.
+ - `alex@@example.com`, `jorge@website.site.com-`, `jack&jane*john@example.com` are not valid.
+- `Address`: Must not be blank or contain only spaces.
+ - Note that entering an address which is blank or has spaces will instead treat a person as having no address. Valid addresses are still only those which do not violate the above criteria.
+- `Note`: Must not be blank or contain only spaces
+ - See above.
+- `Tag`: Should be alphanumeric: [a-zA-Z0-9]
+- `Subject`: Must be `MATH`, `SCIENCE`, `ENGLISH` or `MT`.
+- `Level`: Must be `P1`, `P2`, `P3`, `P4`, `P5` or `P6`.
+- `Appointment`: Must be in the format `START_TIME-END_TIME DAY`
+ - `START_TIME` and `END_TIME` are in the 24-hour format of `HH:MM`. The time of `START_TIME` must strictly be smaller than `END_TIME`
+ - `DAY` must be one of the following: `MON`, `TUE`, `WED`, `THU`, `FRI`, `SAT`,`SUN`.
+ - Respectively, these represent Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, and Sunday.
+ - These are not case-sensitive
+ - `12:00-13:00 MON`, `16:59-22:00 sun` are valid.
+ - `13:00-11:00 MON`, `16:0000-19:1234 MON`, `16:00-17:00 SUNDAY` are not valid.
+ - Overlapping appointments between students are strictly not allowed as TutorRec is for tutors who provide 1-to-1 tutoring.
+
+### Duplicate detection for names and contacts
+
+* TutorRec does not allow for duplicate contacts, and contacts are differentiated by their unique names.
+
+ * Names are not case-sensitive, `John Doe` is the same name as `JOhn dOE`
+
+ * Whitespaces do differentiate names apart, e.g., `Mary Anne` is a different name (and person) from `Maryanne`.
+
+* TutorRec's duplicate detection system ignores case and extra whitespace when comparing names.
+
+ * When adding or editing a contact, if a similar name is detected, regardless of case or whitespace differences, users
+ are warned about potential duplicates.
+
+* In TutorRec, a contact’s phone number and email address do not need to be unique. This flexibility allows you to save the same contact details for parents who have multiple children enrolled with you. As such, it is acceptable for different contacts to share the same phone number and email address.
+
### Viewing help : `help`
-Shows a message explaning how to access the help page.
+Shows a message explaining how to access the help page.

@@ -76,15 +160,37 @@ Format: `help`
Adds a person to the address book.
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+Format: `add n/NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [nt/NOTE] [ap/APPOINTMENT]… [t/TAG]… [s/SUBJECT]… [l/LEVEL]`
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+A person can have any number of tags, and any number of appointments (including 0).
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `add n/Jun Jie p/98765432 e/jj@example.com a/Clementi Ave 3, block 442, #06-01 s/MATH`
+* `add n/Monica Chng e/mc@example.com a/Dempsey Hill p/81888818 ap/10:00-12:00 FRI l/P6`
+* `add n/Abel nt/exstudent ap/12:00-15:00 SUN ap/18:00-22:00 TUE`
+
+Only the "Name" field is mandatory. If you do not wish to have the other fields to have values, you can add the person
+in without the corresponding prefix, or leaving the prefix blank.
+
+For example:
+* `add n/John`
+* `add n/John a/`
+
+Both create the same person in the address book (i.e. a person named "John" with no address).
+The same logic applies to the other fields.
+
+
+
:warning: **Note:**
+New appointments **must not overlap with each other** and should not overlap with existing appointments.
+
+
+
:exclamation: **Caution:**
+When it comes to notes and levels, TutorRec will only consider the final prefix in cases where there are multiple prefixes.
+
+
+For example, with the command: `add n/John l/p1 l/p2`, TutorRec will only consider `l/p2`.
### Listing all persons : `list`
@@ -92,39 +198,59 @@ Shows a list of all persons in the address book.
Format: `list`
+### Viewing a person : `view`
+
+Displays all information of a person on the side window.
+
+Format: `view INDEX`
+
+* Displays all information of the person at the specified index. The index refers to the index number shown in the displayed person list.
+The index **must be a positive integer** 1, 2, 3, ...
+
### Editing a person : `edit`
Edits an existing person in the address book.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [nt/NOTE] [ap/APPOINTMENT]… [t/TAG]… [s/SUBJECT]… [l/LEVEL]`
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
+* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list.
+The index **must be a positive integer** 1, 2, 3, …
* At least one of the optional fields must be provided.
* Existing values will be updated to the input values.
* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+ specifying any tags after it. Appointments work similarly (i.e. typing `ap/` with no appointments after it clears all appointments)
+* You can remove fields (except for name) by typing the prefix for the relevant field and leaving it blank.
+* Note that while the edit command format specifies that the fields `nt/` and `l/` accepts one argument only,
+ TutorRec v1.4 still accept multiple inputs, however, only the last input will be processed.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `edit 1 p/91234567 e/jj@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `jj@example.com` respectively.
+* `edit 2 n/Monica Chng t/` Edits the name of the 2nd person to be `Monica Chng` and clears all existing tags.
+* `edit 3 n/Bobby Brown p/` Edits the name of the 3rd person to be `Bobby Brown` and removes the `phone` field.
+
+
:exclamation: **Caution:**
+When it comes to notes and levels, TutorRec will only consider the final prefix in cases where there are multiple prefixes.
+
-### Locating persons by name: `find`
+For example, with the command: `edit 1 l/p1 l/p2`, TutorRec will only consider `l/p2`.
+
+### Locating persons by name : `find`
Finds persons whose names contain any of the given keywords.
Format: `find KEYWORD [MORE_KEYWORDS]`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
+* 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`
+* Only full words will be matched e.g. `Han` will not match `Hans`.
* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+ e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
+* `find Jun` returns `jun` and `Jun Jie`.
+* `find alex david` returns `Alex Yeoh`, `David Li`.

### Deleting a person : `delete`
@@ -139,7 +265,22 @@ Format: `delete INDEX`
Examples:
* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `find Monica` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+
+### Viewing all appointments : `appointments`
+
+Displays all appointments of persons currently displayed in the list, sorted, along with the persons involved.
+Optionally, you may specify a `DAY` or multiple `DAY`s to further restrict the appointments displayed.
+
+Format: `appointments [DAY]`
+
+* `DAY` must be one of `MON`, `TUE`, ..., `SUN`.
+* `[DAY]` may be empty.
+
+Examples:
+* `appointments` returns all appointments among the displayed persons.
+* `appointments MON` returns all appointments among the displayed persons on Monday.
+* `appointments MON TUE` returns all appointments among the displayed persons on Monday and Tuesday.
### Clearing all entries : `clear`
@@ -155,15 +296,15 @@ Format: `exit`
### Saving the data
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+TutorRec data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
### Editing the data file
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+TutorRec data is saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+If your changes to the data file makes its format invalid, TutorRec will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause TutorRec to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
### Archiving data files `[coming in v2.0]`
@@ -175,7 +316,7 @@ _Details coming soon ..._
## FAQ
**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**A**: Install the app in the other computer and overwrite the data file it creates with the file that contains the data in your previous TutorRec home folder.
--------------------------------------------------------------------------------------------------------------------
@@ -189,10 +330,13 @@ _Details coming soon ..._
Action | Format, Examples
--------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
+**Add** | `add n/NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [nt/NOTE] [ap/APPOINTMENT]… [t/TAG]… [s/SUBJECT]… [l/LEVEL]` e.g., `add n/Jun Jie p/98765432 e/jj@example.com a/Clementi Ave 3, block 442, #06-01 s/MATH`
+**View** | `appointments`
**Clear** | `clear`
**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
+**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [nt/NOTE] [ap/APPOINTMENT]… [t/TAG]… [s/SUBJECT]… [l/LEVEL]` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
**List** | `list`
+**View person details** | `view INDEX`
**Help** | `help`
+**Exit** | `exit`
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..a7610eca789 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "TutorRec"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2324S2-CS2103-F09-3/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..09d966ad93a 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "TutorRec";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddAppointmentSequenceDiagramCheck.puml b/docs/diagrams/AddAppointmentSequenceDiagramCheck.puml
new file mode 100644
index 00000000000..e9ede3a38c5
--- /dev/null
+++ b/docs/diagrams/AddAppointmentSequenceDiagramCheck.puml
@@ -0,0 +1,40 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":AddCommand" as AddCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "toAdd:Person" as Person MODEL_COLOR
+participant "model:Model" as Model MODEL_COLOR
+participant "aps:AppointmentList" as AppointmentList MODEL_COLOR
+end box
+
+[-> AddCommand : checkAppointmentOverlap(model)
+activate AddCommand
+
+AddCommand -> Person : getAppointments()
+activate Person
+
+Person -> AddCommand : aps
+deactivate Person
+
+AddCommand -> Model : appointmentsOverlap(aps)
+activate Model
+
+Model -> AddCommand : false
+deactivate Model
+
+AddCommand -> AppointmentList : isOverlapping()
+activate AppointmentList
+
+AppointmentList -> AddCommand : false
+deactivate
+
+[<- AddCommand
+
+destroy AddCommand
+
+@enduml
diff --git a/docs/diagrams/AddAppointmentSequenceDiagramMain.puml b/docs/diagrams/AddAppointmentSequenceDiagramMain.puml
new file mode 100644
index 00000000000..9319beda2ca
--- /dev/null
+++ b/docs/diagrams/AddAppointmentSequenceDiagramMain.puml
@@ -0,0 +1,42 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":AddCommand" as AddCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+end box
+
+[-> AddCommand : execute(model)
+activate AddCommand
+
+AddCommand -> Model : hasPerson(toAdd)
+activate Model
+
+Model --> AddCommand :
+deactivate Model
+
+
+AddCommand -> AddCommand : checkAppointmentOverlap(model)
+activate AddCommand
+
+
+AddCommand -> AddCommand
+deactivate AddCommand
+
+
+AddCommand -> Model : addPerson(toAdd)
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+[<- AddCommand : result
+|||
+
+destroy AddCommand
+
+@enduml
diff --git a/docs/diagrams/AddSequenceDiagramRefFrame.puml b/docs/diagrams/AddSequenceDiagramRefFrame.puml
new file mode 100644
index 00000000000..aa893285472
--- /dev/null
+++ b/docs/diagrams/AddSequenceDiagramRefFrame.puml
@@ -0,0 +1,41 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":ParserUtil" as ParserUtil MODEL_COLOR
+participant "ApptSet: Set" as apptSet #Green
+participant "a:Appointment" as Appointment #Green
+end box
+
+
+activate Model
+
+Model -> ParserUtil : parseAppointments(appointments)
+activate ParserUtil
+ParserUtil -> ParserUtil : requireNonNull(appointments)
+create apptSet
+ParserUtil -> apptSet : new
+activate apptSet
+
+loop Each String in appointments
+ create Appointment
+ apptSet -> Appointment : Appointment(a)
+ Activate Appointment
+ Appointment -> Appointment : checkArgument(appointment)
+ Appointment -> apptSet : add(a)
+ deactivate Appointment
+end
+
+apptSet -> ParserUtil : apptSet
+deactivate apptSet
+
+ParserUtil -> Model : apptSet
+
+deactivate ParserUtil
+
+deactivate Model
+
+@enduml
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 598474a5c82..c74cd2518cd 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -4,18 +4,23 @@ skinparam arrowThickness 1.1
skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
-UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList -[hidden]down- UniquePersonList
+AddressBook *-down-> "1" UniquePersonList
+AddressBook *-down-> "1" UniqueTagList
+AddressBook *-down-> "1" UniqueAppointmentList
+UniquePersonList -[hidden]right- UniqueAppointmentList
+UniquePersonList -[hidden]left- UniqueTagList
+Tag -[hidden]right- Appointment
-UniqueTagList -right-> "*" Tag
-UniquePersonList -right-> Person
+UniqueTagList -down-> "*" Tag
+UniqueAppointmentList -down-> "*" Appointment
+UniquePersonList -down-> Person
Person -up-> "*" Tag
+Person -up-> "*" Appointment
Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
+Person *--> "*" Note
@enduml
diff --git a/docs/diagrams/ListSequenceDiagram.puml b/docs/diagrams/ListSequenceDiagram.puml
new file mode 100644
index 00000000000..5141f83d19a
--- /dev/null
+++ b/docs/diagrams/ListSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "l:ListCommand" as ListCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute(list)
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand(list)
+activate AddressBookParser
+
+create ListCommand
+AddressBookParser -> ListCommand
+activate ListCommand
+
+ListCommand --> AddressBookParser
+deactivate ListCommand
+
+AddressBookParser --> LogicManager : l
+deactivate AddressBookParser
+
+LogicManager -> ListCommand : execute()
+activate ListCommand
+
+ListCommand -> Model : updateFilteredPersonList()
+activate Model
+
+
+deactivate Model
+
+ListCommand --> LogicManager : result
+deactivate ListCommand
+ListCommand -[hidden]-> LogicManager : result
+destroy ListCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..20469001469 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -13,12 +13,18 @@ Class ModelManager
Class UserPrefs
Class UniquePersonList
+Class DisjointAppointmentList
+Class AppointmentList
Class Person
Class Address
Class Email
Class Name
Class Phone
Class Tag
+Class Appointment
+Class Note
+class Subject
+class Level
Class I #FFFFFF
}
@@ -35,20 +41,32 @@ ModelManager -left-> "1" AddressBook
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
+AddressBook *-down-> "1" DisjointAppointmentList
+DisjointAppointmentList -[hidden]r-> UniquePersonList
+AddressBook *-down-> "1" UniquePersonList
UniquePersonList --> "~* all" Person
Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
Person *--> "*" Tag
+Person *--> Note
+Person *--> Level
+Person *--> "0...4" Subject
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
+Person *-left-> "1" AppointmentList
+AppointmentList -up-> "*" Appointment
+DisjointAppointmentList --> "~* all" Appointment
+
Name -[hidden]right-> Phone
Phone -[hidden]right-> Address
Address -[hidden]right-> Email
+Email -[hidden]right-> Tag
+Tag -[hidden]right-> Subject
+Subject -[hidden]right-> Level
ModelManager --> "~* filtered" Person
@enduml
diff --git a/docs/diagrams/add-remark/RemarkClass.puml b/docs/diagrams/NoteClass.puml
similarity index 67%
rename from docs/diagrams/add-remark/RemarkClass.puml
rename to docs/diagrams/NoteClass.puml
index 019c1ecbbf1..c281d095cdc 100644
--- a/docs/diagrams/add-remark/RemarkClass.puml
+++ b/docs/diagrams/NoteClass.puml
@@ -5,15 +5,14 @@ skinparam classAttributeIconSize 0
Class "{abstract}\nCommand" as Command {
+execute(Model): CommandResult
}
-Class RemarkCommand {
+Class NoteCommand {
+COMMAND_WORD: String
+MESSAGE_USAGE: String
- +MESSAGE_NOT_IMPLEMENTED_YET: String
+execute(Model): CommandResult
}
Class CommandException
-RemarkCommand -up-|> Command
+NoteCommand -up-|> Command
Command ..> CommandException: throws >
-RemarkCommand .right.> CommandException: throws >
+NoteCommand .right.> CommandException: throws >
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..02c16e9dbfb 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -1,7 +1,7 @@
@startuml
!include style.puml
-skinparam arrowThickness 1.1
-skinparam arrowColor UI_COLOR_T4
+skinparam arrowThickness 1.0
+skinparam arrowColor UI_COLOR_T3
skinparam classBackgroundColor UI_COLOR
package UI <>{
diff --git a/docs/images/AddAppointmentSequenceDiagramCheck.png b/docs/images/AddAppointmentSequenceDiagramCheck.png
new file mode 100644
index 00000000000..1dceb5ef497
Binary files /dev/null and b/docs/images/AddAppointmentSequenceDiagramCheck.png differ
diff --git a/docs/images/AddAppointmentSequenceDiagramMain.png b/docs/images/AddAppointmentSequenceDiagramMain.png
new file mode 100644
index 00000000000..0ab37f59d4c
Binary files /dev/null and b/docs/images/AddAppointmentSequenceDiagramMain.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 02a42e35e76..03f9414c089 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/ListSequenceDiagram.png b/docs/images/ListSequenceDiagram.png
new file mode 100644
index 00000000000..f11a400c3fb
Binary files /dev/null and b/docs/images/ListSequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..8c327279236 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/NoteClass.png b/docs/images/NoteClass.png
new file mode 100644
index 00000000000..07bbaf1dea8
Binary files /dev/null and b/docs/images/NoteClass.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..0698cd09740 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 11f06d68671..781d81ec3f2 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/biinnnggggg.png b/docs/images/biinnnggggg.png
new file mode 100644
index 00000000000..00801fcaa0b
Binary files /dev/null and b/docs/images/biinnnggggg.png differ
diff --git a/docs/images/johndoe.png b/docs/images/darylgolden.png
similarity index 100%
rename from docs/images/johndoe.png
rename to docs/images/darylgolden.png
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
index 235da1c273e..ed20cdae62b 100644
Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
index b1f70470137..6f1e02689d5 100644
Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ
diff --git a/docs/images/jonchong98.png b/docs/images/jonchong98.png
new file mode 100644
index 00000000000..875b3d1c11a
Binary files /dev/null and b/docs/images/jonchong98.png differ
diff --git a/docs/images/tanqinyong.png b/docs/images/tanqinyong.png
new file mode 100644
index 00000000000..d7540ed1cbe
Binary files /dev/null and b/docs/images/tanqinyong.png differ
diff --git a/docs/images/topkec.png b/docs/images/topkec.png
new file mode 100644
index 00000000000..11a4a0e692c
Binary files /dev/null and b/docs/images/topkec.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..776a3ab53b4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,17 @@
---
layout: page
-title: AddressBook Level-3
+title: TutorRec
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+[](https://github.com/AY2324S2-CS2103-F09-3/tp/actions)
+[](https://codecov.io/gh/AY2324S2-CS2103-F09-3/tp)

-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**TutorRec is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+* If you are interested in using TutorRec, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing TutorRec, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..97240a565d5 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -36,7 +36,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(0, 3, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java
index a97a86ee8d7..56ffe34340e 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/seedu/address/commons/core/GuiSettings.java
@@ -12,8 +12,8 @@
*/
public class GuiSettings implements Serializable {
- private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_HEIGHT = 550;
+ private static final double DEFAULT_WIDTH = 880;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..8e062c411d4 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -11,6 +11,7 @@
* Helper functions for handling strings.
*/
public class StringUtil {
+ public static final String SEPARATOR = "\n------------------------------------------\n";
/**
* Returns true if the {@code sentence} contains the {@code word}.
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..eaf30fe0369 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -36,15 +36,32 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
*/
public static String format(Person person) {
final StringBuilder builder = new StringBuilder();
- builder.append(person.getName())
- .append("; Phone: ")
- .append(person.getPhone())
- .append("; Email: ")
- .append(person.getEmail())
- .append("; Address: ")
- .append(person.getAddress())
- .append("; Tags: ");
+ builder.append(person.getName());
+ if (!person.getPhone().isEmpty()) {
+ builder.append("; Phone: ").append(person.getPhone());
+ }
+ if (!person.getEmail().isEmpty()) {
+ builder.append("; Email: ").append(person.getEmail());
+ }
+ if (!person.getAddress().isEmpty()) {
+ builder.append("; Address: ").append(person.getAddress());
+ }
+ if (!person.getNote().isEmpty()) {
+ builder.append("; Note: ").append(person.getNote());
+ }
+ builder.append("; Tags: ");
person.getTags().forEach(builder::append);
+
+ builder.append("; Appointments: ");
+ builder.append(person.getAppointments());
+
+ builder.append("; Subjects: ");
+ person.getSubjects().forEach(builder::append);
+
+ if (!person.getLevel().isEmpty()) {
+ builder.append("; Level: ").append(person.getLevel());
+ }
+
return builder.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..483c29b9789 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -2,15 +2,21 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.List;
+
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.AppointmentList;
+import seedu.address.model.appointment.DisjointAppointmentList;
import seedu.address.model.person.Person;
/**
@@ -26,17 +32,23 @@ public class AddCommand extends Command {
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + PREFIX_NOTE + "NOTE"
+ + "[" + PREFIX_APPOINTMENT + "APPOINTMENT]"
+ + "[" + PREFIX_TAG + "TAG] ...\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ + PREFIX_NOTE + "Went to fetch red paint "
+ + PREFIX_APPOINTMENT + "12:00-13:00 TUE "
+ + PREFIX_APPOINTMENT + "14:00-17:00 SUN "
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_NEAR_DUPLICATES = "New person added: %1$s \nPossible duplicate contacts: %2$s";
private final Person toAdd;
@@ -56,10 +68,35 @@ public CommandResult execute(Model model) throws CommandException {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ checkAppointmentOverlap(model);
+
+ // Duplicate Detection feature
+ List duplicateNames = model.findNearDuplicates(toAdd);
model.addPerson(toAdd);
+
+ // If there are near duplicate names
+ if (!duplicateNames.isEmpty()) {
+ return new CommandResult(String.format(MESSAGE_NEAR_DUPLICATES,
+ Messages.format(toAdd),
+ String.join(", ", duplicateNames)));
+ }
+
return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
}
+ private void checkAppointmentOverlap(Model model) throws CommandException {
+ // between appointments to be added and existing appointments
+ AppointmentList appointments = toAdd.getAppointments();
+ if (model.appointmentsOverlap(appointments.asUnmodifiableObservableList())) {
+ throw new CommandException(DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }
+
+ // between two appointments to be added
+ if (appointments.isOverlapping()) {
+ throw new CommandException(DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..440d87a2419 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -2,9 +2,13 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
@@ -14,6 +18,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
@@ -21,11 +26,15 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
@@ -43,7 +52,11 @@ public class EditCommand extends Command {
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_NOTE + "NOTE]"
+ + "[" + PREFIX_APPOINTMENT + "APPOINTMENT]"
+ + "[" + PREFIX_TAG + "TAG] ...\n"
+ + "[" + PREFIX_SUBJECT + "SUBJECT] "
+ + "[" + PREFIX_LEVEL + "LEVEL]\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
@@ -51,7 +64,9 @@ public class EditCommand extends Command {
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_NEAR_DUPLICATES = "Edited Person: %1$s \nPossible duplicate contacts: %2$s";
+ public static final String MESSAGE_OVERLAPPING_APPOINTMENT =
+ "This person's appointments clash with an existing appointment";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
@@ -83,8 +98,37 @@ public CommandResult execute(Model model) throws CommandException {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ // Duplicate Detection feature
+ List duplicateNames = model.findNearDuplicates(editedPerson);
+
+ // Overlapping appointment detection
+ if (editedPerson.getAppointments().isOverlapping()) {
+ throw new CommandException(MESSAGE_OVERLAPPING_APPOINTMENT);
+ }
+
+ AppointmentList editedAppointmentList = new AppointmentList();
+ editedAppointmentList.setAppointments(editedPerson.getAppointments());
+ editedAppointmentList.addAll(model.getFilteredAppointmentList()
+ .stream()
+ .filter(appointment -> !(personToEdit.getAppointments().contains(appointment)))
+ .collect(Collectors.toList()));
+
+ if (editedAppointmentList.isOverlapping()) {
+ throw new CommandException(MESSAGE_OVERLAPPING_APPOINTMENT);
+ }
+
model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+
+ // If there are near duplicate names
+ if (!duplicateNames.isEmpty()) {
+ // remove the old name as it will be detected
+ duplicateNames.remove(personToEdit.getName().toString());
+ return new CommandResult(String.format(MESSAGE_NEAR_DUPLICATES,
+ Messages.format(editedPerson),
+ String.join(", ", duplicateNames)));
+ }
+
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
}
@@ -99,9 +143,17 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Note updatedNote = editPersonDescriptor.getNote().orElse(personToEdit.getNote());
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ AppointmentList updatedAppointments = editPersonDescriptor
+ .getAppointments().orElse(personToEdit.getAppointments());
+ Set updatedSubjects = editPersonDescriptor.getSubjects().orElse(personToEdit.getSubjects());
+ Level updatedLevel = editPersonDescriptor.getLevel().orElse(personToEdit.getLevel());
+
+ return new Person(
+ updatedName, updatedPhone, updatedEmail, updatedAddress, updatedNote, updatedTags,
+ updatedAppointments, updatedSubjects, updatedLevel
+ );
}
@Override
@@ -137,7 +189,11 @@ public static class EditPersonDescriptor {
private Phone phone;
private Email email;
private Address address;
+ private Note note;
private Set tags;
+ private AppointmentList appointments;
+ private Set subjects;
+ private Level level;
public EditPersonDescriptor() {}
@@ -150,14 +206,18 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setPhone(toCopy.phone);
setEmail(toCopy.email);
setAddress(toCopy.address);
+ setNote(toCopy.note);
setTags(toCopy.tags);
+ setAppointments(toCopy.appointments);
+ setSubjects(toCopy.subjects);
+ setLevel(toCopy.level);
}
/**
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, note, tags, appointments, subjects, level);
}
public void setName(Name name) {
@@ -192,6 +252,14 @@ public Optional getAddress() {
return Optional.ofNullable(address);
}
+ public void setNote(Note note) {
+ this.note = note;
+ }
+
+ public Optional getNote() {
+ return Optional.ofNullable(note);
+ }
+
/**
* Sets {@code tags} to this object's {@code tags}.
* A defensive copy of {@code tags} is used internally.
@@ -208,6 +276,59 @@ public void setTags(Set tags) {
public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ /**
+ * Sets {@code appointments} to this object's {@code appointments}.
+ * A defensive copy of {@code appointments} is used internally.
+ */
+ public void setAppointments(AppointmentList appointments) {
+ if (appointments == null) {
+ this.appointments = null;
+ return;
+ }
+ this.appointments = new AppointmentList();
+ this.appointments.addAll(appointments.asUnmodifiableObservableList());
+ }
+ /**
+ * Returns an unmodifiable appointment set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code appointments} is null.
+ */
+ public Optional getAppointments() {
+ if (appointments == null) {
+ return Optional.empty();
+ }
+ AppointmentList defensiveCopy = new AppointmentList();
+ defensiveCopy.addAll(appointments.asUnmodifiableObservableList());
+ return Optional.of(defensiveCopy);
+ }
+
+ /**
+ * Sets {@code subjects} to this object's {@code subjects}.
+ * A defensive copy of {@code subjects} is used internally.
+ */
+ public void setSubjects(Set subjects) {
+ this.subjects = (subjects != null) ? new HashSet<>(subjects) : null;
+ }
+
+ /**
+ * Returns an unmodifiable subject set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code subjects} is null.
+ */
+ public Optional> getSubjects() {
+ return (subjects != null) ? Optional.of(Collections.unmodifiableSet(subjects)) : Optional.empty();
+ }
+
+ /**
+ * Sets {@code level} to this object's {@code level}.
+ */
+ public void setLevel(Level level) {
+ this.level = level;
+ }
+
+ public Optional getLevel() {
+ return Optional.ofNullable(level);
+ }
@Override
public boolean equals(Object other) {
@@ -225,7 +346,11 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(note, otherEditPersonDescriptor.note)
+ && Objects.equals(tags, otherEditPersonDescriptor.tags)
+ && Objects.equals(appointments, otherEditPersonDescriptor.appointments)
+ && Objects.equals(subjects, otherEditPersonDescriptor.subjects)
+ && Objects.equals(level, otherEditPersonDescriptor.level);
}
@Override
@@ -235,7 +360,11 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
+ .add("note", note)
+ .add("appointments", appointments)
.add("tags", tags)
+ .add("subjects", subjects)
+ .add("level", level)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ViewAppointmentsCommand.java b/src/main/java/seedu/address/logic/commands/ViewAppointmentsCommand.java
new file mode 100644
index 00000000000..4b3b990cfda
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ViewAppointmentsCommand.java
@@ -0,0 +1,80 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javafx.util.Pair;
+import seedu.address.model.Model;
+import seedu.address.model.appointment.AppointmentIsDayOfWeekPredicate;
+import seedu.address.model.person.Person;
+
+/**
+ * Shows all appointments in the filtered address book.
+ */
+public class ViewAppointmentsCommand extends Command {
+
+ public static final String COMMAND_WORD = "appointments";
+
+ public static final String MESSAGE_SUCCESS = "Listed all appointments";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Lists all appointments of persons displayed.\n"
+ + "Example: " + COMMAND_WORD + "\n"
+ + "or\n"
+ + COMMAND_WORD + " [DAY_OF_WEEK]: Lists all appointments on the days of the week specified.\n"
+ + "Example: " + COMMAND_WORD + " MON\n, " + COMMAND_WORD + " MON TUE\n";
+
+ private final AppointmentIsDayOfWeekPredicate predicate;
+
+ public ViewAppointmentsCommand(AppointmentIsDayOfWeekPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ // Get all appointments from the last shown list of persons
+ List appointments = lastShownList.stream()
+ .flatMap(person -> person.hasAppointments()
+ ? person.getAppointments()
+ .asUnmodifiableObservableList().stream()
+ .filter(predicate)
+ .map(appointment -> new Pair<>(appointment, person.getName().toString()))
+ : Stream.empty())
+ .sorted((o1, o2) -> o1.getKey().compareTo(o2.getKey())) // comparing by appointment only
+ .map(pair -> pair.getValue() + ": " + pair.getKey().toString())
+ .collect(Collectors.toList());
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Appointments:\n");
+ for (String appointment : appointments) {
+ sb.append(appointment).append("\n");
+ }
+
+ if (appointments.isEmpty()) {
+ sb.append("There are no appointments to show!");
+ }
+
+ return new CommandResult(sb.toString().trim());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ViewAppointmentsCommand)) {
+ return false;
+ }
+
+ ViewAppointmentsCommand otherCommand = (ViewAppointmentsCommand) other;
+ return predicate.equals(otherCommand.predicate);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java
new file mode 100644
index 00000000000..82c5087f6b7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java
@@ -0,0 +1,72 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Displays all information of a contact in resultDisplay based on it's displayed index.
+ *
+ * Code is heavily borrowed from existing DeleteCommand.java due to extreme similarities in desired implementation.
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays all information about a person "
+ + "identified by the index number used in the displayed person list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ public ViewCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToView = lastShownList.get(targetIndex.getZeroBased());
+ List viewDetails = personToView.getViewDetails();
+ StringBuilder sb = new StringBuilder();
+ for (String detail : viewDetails) {
+ sb.append(detail);
+ }
+ return new CommandResult(sb.toString().trim());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ViewCommand)) {
+ return false;
+ }
+
+ ViewCommand otherViewCommand = (ViewCommand) other;
+ return targetIndex.equals(otherViewCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..b3cc3fa8de4 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,10 +1,15 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.INCORRECT_PREFIX_MAP;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Set;
@@ -12,11 +17,15 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
@@ -31,21 +40,26 @@ public class AddCommandParser implements Parser {
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, INCORRECT_PREFIX_MAP, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_NOTE, PREFIX_TAG, PREFIX_APPOINTMENT, PREFIX_SUBJECT, PREFIX_LEVEL);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).orElse(null));
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).orElse(null));
+ Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).orElse(null));
+ Note note = ParserUtil.parseNote(argMultimap.getValue(PREFIX_NOTE).orElse(null));
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ AppointmentList appointmentList = ParserUtil.parseAppointments(argMultimap.getAllValues(PREFIX_APPOINTMENT));
+ Set subjectList = ParserUtil.parseSubjects(argMultimap.getAllValues(PREFIX_SUBJECT));
+ Level level = ParserUtil.parseLevel(argMultimap.getValue(PREFIX_LEVEL).orElse(null));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, address, note, tagList, appointmentList, subjectList, level);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..f97c8dcff7c 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -17,6 +17,8 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ViewAppointmentsCommand;
+import seedu.address.logic.commands.ViewCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -77,6 +79,12 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case ViewAppointmentsCommand.COMMAND_WORD:
+ return new ViewAppointmentsCommandParser().parse(arguments);
+
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..f4619e0e1d3 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
@@ -2,7 +2,9 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -19,13 +21,37 @@ public class ArgumentTokenizer {
* Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their
* respective argument values. Only the given prefixes will be recognized in the arguments string.
*
- * @param argsString Arguments string of the form: {@code preamble value value ...}
- * @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @param argsString Arguments string of the form: {@code preamble value value ...}
+ * @param incorrectPrefixes A list of incorrect prefixes to check against.
+ * @param prefixes Prefixes to tokenize the arguments string with
+ * @return ArgumentMultimap object that maps prefixes to their arguments
*/
- public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
- List positions = findAllPrefixPositions(argsString, prefixes);
- return extractArguments(argsString, positions);
+ public static ArgumentMultimap tokenize(String argsString, HashMap> incorrectPrefixes, Prefix... prefixes) {
+ String correctedArgsString = fixIncorrectPrefixes(argsString, incorrectPrefixes);
+ List positions = findAllPrefixPositions(correctedArgsString, prefixes);
+ return extractArguments(correctedArgsString, positions);
+ }
+
+ /**
+ * Corrects incorrect prefixes in an arguments string using a provided mapping.
+ *
+ * @param argsString The string containing potentially incorrect prefixes.
+ * @param incorrectPrefixes A map of correct prefixes to their respective list of common incorrect variations.
+ * @return The corrected arguments string with all known incorrect prefixes replaced by their correct versions.
+ */
+ public static String fixIncorrectPrefixes(String argsString, HashMap> incorrectPrefixes) {
+ // Iterate over each entry in the incorrectPrefixes map
+ for (Map.Entry> entry : incorrectPrefixes.entrySet()) {
+ Prefix correctPrefix = entry.getKey();
+ List incorrectPrefixList = entry.getValue();
+
+ // Replace each incorrect prefix with the correct one
+ for (String incorrectPrefix : incorrectPrefixList) {
+ argsString = argsString.replace(incorrectPrefix, correctPrefix.getPrefix());
+ }
+ }
+ return argsString;
}
/**
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..4dc5a9bc3ef 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -1,5 +1,10 @@
package seedu.address.logic.parser;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+
/**
* Contains Command Line Interface (CLI) syntax definitions common to multiple commands
*/
@@ -10,6 +15,33 @@ public class CliSyntax {
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_NOTE = new Prefix("nt/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_APPOINTMENT = new Prefix("ap/");
+ public static final Prefix PREFIX_SUBJECT = new Prefix("s/");
+ public static final Prefix PREFIX_LEVEL = new Prefix("l/");
+
+ /*
+ Incorrect but acceptable prefixes. We may add more as required.
+ */
+ public static final HashMap> INCORRECT_PREFIX_MAP = new HashMap<>();
+
+ static {
+ // Populate the map with common incorrect prefixes
+ INCORRECT_PREFIX_MAP.put(PREFIX_NAME, Arrays.asList("name/", "nae/", "nam/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_PHONE, Arrays.asList("phone/", "phon/", "pho/", "ph/", "hp/", "/handphone"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_EMAIL, Arrays.asList("email/", "emai/", "eml/", "em/", "ema/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_ADDRESS, Arrays.asList("address/", "addr/", "add/",
+ "ad/", "addres/", "adress/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_NOTE, Arrays.asList("note/", "not/", "nt/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_TAG, Arrays.asList("tag/", "ta/", "tg/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_APPOINTMENT, Arrays.asList("appointment/", "appt/", "apt/",
+ "appoint/", "app/", "appointmen/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_SUBJECT, Arrays.asList("subject/", "subj/", "sub/",
+ "subjec/", "subjet/", "subje/", "su/", "ubject/"));
+ INCORRECT_PREFIX_MAP.put(PREFIX_LEVEL, Arrays.asList("level/", "lvl/", "leve/",
+ "lv/", "le/", "lev/", "lvel/", "evel/"));
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..fcb9aa684b0 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,10 +2,15 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.INCORRECT_PREFIX_MAP;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Collection;
@@ -17,6 +22,8 @@
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.appointment.AppointmentList;
+import seedu.address.model.appointment.exceptions.OverlappingAppointmentException;
import seedu.address.model.tag.Tag;
/**
@@ -32,7 +39,8 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, INCORRECT_PREFIX_MAP, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_NOTE, PREFIX_TAG, PREFIX_APPOINTMENT, PREFIX_SUBJECT, PREFIX_LEVEL);
Index index;
@@ -50,15 +58,28 @@ public EditCommand parse(String args) throws ParseException {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
- editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).orElse(null)));
}
if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
- editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
+ editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).orElse(null)));
}
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
- editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).orElse(null)));
+ }
+ if (argMultimap.getValue(PREFIX_NOTE).isPresent()) {
+ editPersonDescriptor.setNote(ParserUtil.parseNote(argMultimap.getValue(PREFIX_NOTE).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) {
+ editPersonDescriptor.setSubjects(ParserUtil.parseSubjects(argMultimap.getAllValues(PREFIX_SUBJECT)));
+ }
+
+ if (argMultimap.getValue(PREFIX_LEVEL).isPresent()) {
+ editPersonDescriptor.setLevel(ParserUtil.parseLevel(argMultimap.getValue(PREFIX_LEVEL).orElse(null)));
}
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+ parseAppointmentsForEdit(argMultimap.getAllValues(PREFIX_APPOINTMENT))
+ .ifPresent(editPersonDescriptor::setAppointments);
if (!editPersonDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
@@ -81,5 +102,22 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars
Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
return Optional.of(ParserUtil.parseTags(tagSet));
}
+ /**
+ * Parses {@code Collection appointments} into an {@code AppointmentList}
+ * if {@code appointments} is non-empty.
+ * If {@code appointments} contain only one element which is an empty string, it will be parsed into a
+ * {@code AppointmentList} containing zero appointments.
+ */
+ private Optional parseAppointmentsForEdit(Collection appointments) throws ParseException,
+ OverlappingAppointmentException {
+ assert appointments != null;
+
+ if (appointments.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection appointmentSet =
+ appointments.size() == 1 && appointments.contains("") ? Collections.emptySet() : appointments;
+ return Optional.of(ParserUtil.parseAppointments(appointmentSet));
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..2be5096c3bf 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -9,10 +9,20 @@
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.Appointment;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmptyAddress;
+import seedu.address.model.person.EmptyEmail;
+import seedu.address.model.person.EmptyLevel;
+import seedu.address.model.person.EmptyNote;
+import seedu.address.model.person.EmptyPhone;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
@@ -57,8 +67,13 @@ public static Name parseName(String name) throws ParseException {
* @throws ParseException if the given {@code phone} is invalid.
*/
public static Phone parsePhone(String phone) throws ParseException {
- requireNonNull(phone);
+ if (phone == null) {
+ return new EmptyPhone();
+ }
String trimmedPhone = phone.trim();
+ if (phone.equals("")) {
+ return new EmptyPhone();
+ }
if (!Phone.isValidPhone(trimmedPhone)) {
throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
}
@@ -72,8 +87,13 @@ public static Phone parsePhone(String phone) throws ParseException {
* @throws ParseException if the given {@code address} is invalid.
*/
public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
+ if (address == null) {
+ return new EmptyAddress();
+ }
String trimmedAddress = address.trim();
+ if (address.equals("")) {
+ return new EmptyAddress();
+ }
if (!Address.isValidAddress(trimmedAddress)) {
throw new ParseException(Address.MESSAGE_CONSTRAINTS);
}
@@ -87,8 +107,13 @@ public static Address parseAddress(String address) throws ParseException {
* @throws ParseException if the given {@code email} is invalid.
*/
public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
+ if (email == null) {
+ return new EmptyEmail();
+ }
String trimmedEmail = email.trim();
+ if (email.equals("")) {
+ return new EmptyEmail();
+ }
if (!Email.isValidEmail(trimmedEmail)) {
throw new ParseException(Email.MESSAGE_CONSTRAINTS);
}
@@ -110,6 +135,23 @@ public static Tag parseTag(String tag) throws ParseException {
return new Tag(trimmedTag);
}
+ /**
+ * Parses a {@code String note} into a {@code Note}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code note} is invalid.
+ */
+ public static Note parseNote(String note) throws ParseException {
+ if (note == null) {
+ return new EmptyNote();
+ }
+ String trimmedNote = note.trim();
+ if (note.equals("")) {
+ return new EmptyNote();
+ }
+ return new Note(trimmedNote);
+ }
+
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
@@ -117,8 +159,96 @@ public static Set parseTags(Collection tags) throws ParseException
requireNonNull(tags);
final Set tagSet = new HashSet<>();
for (String tagName : tags) {
+ if (tagName.isEmpty()) {
+ continue;
+ }
tagSet.add(parseTag(tagName));
}
return tagSet;
}
+ /**
+ * Parses a {@code String appointment} into a {@code Appointment}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code appointment} is invalid.
+ */
+ public static Appointment parseAppointment(String appointment) throws ParseException {
+ requireNonNull(appointment);
+ String trimmedAppointment = appointment.trim();
+ if (!Appointment.isValidAppointment(trimmedAppointment)) {
+ throw new ParseException(Appointment.MESSAGE_CONSTRAINTS);
+ }
+ return new Appointment(trimmedAppointment);
+ }
+ /**
+ * Parses {@code Collection appointment} into an {@code AppointmentList}.
+ */
+ public static AppointmentList parseAppointments(Collection appointments) throws ParseException {
+ requireNonNull(appointments);
+ final AppointmentList appointmentList = new AppointmentList();
+ for (String ap : appointments) {
+ if (ap.isEmpty()) {
+ continue;
+ }
+ Appointment appointment = parseAppointment(ap);
+ /*if (appointmentList.overlaps(appointment)) {
+ throw new ParseException(DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }*/
+ appointmentList.add(appointment);
+ }
+ return appointmentList;
+ }
+
+ /**
+ * Parses a {@code String subject} into a {@code Subject}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code subject} is invalid.
+ */
+ private static Subject parseSubject(String subject) throws ParseException {
+ if (subject == null) {
+ return null;
+ }
+ String trimmedSubject = subject.trim();
+ if (trimmedSubject.equals("")) {
+ return null;
+ }
+ if (!Subject.isValidSubject(trimmedSubject)) {
+ throw new ParseException(Subject.MESSAGE_CONSTRAINTS);
+ }
+ return new Subject(trimmedSubject);
+ }
+
+ /**
+ * Parses {@code Collection subjects} into a {@code Set}.
+ */
+ public static Set parseSubjects(Collection subjects) throws ParseException {
+ requireNonNull(subjects);
+ final Set subjectSet = new HashSet<>();
+ for (String subjectName : subjects) {
+ Subject s = parseSubject(subjectName);
+ if (s == null) {
+ continue;
+ }
+ subjectSet.add(s);
+ }
+ return subjectSet;
+ }
+
+ /**
+ * Parses a {@code String level} into a {@code Level}.
+ */
+ public static Level parseLevel(String level) throws ParseException {
+ if (level == null) {
+ return new EmptyLevel();
+ }
+ String trimmedLevel = level.trim();
+ if (trimmedLevel.equals("")) {
+ return new EmptyLevel();
+ }
+ if (!Level.isValidLevel(level)) {
+ throw new ParseException(Level.MESSAGE_CONSTRAINTS);
+ }
+ return new Level(level);
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ViewAppointmentsCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewAppointmentsCommandParser.java
new file mode 100644
index 00000000000..a04817f7f76
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ViewAppointmentsCommandParser.java
@@ -0,0 +1,53 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.time.DayOfWeek;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.ViewAppointmentsCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentIsDayOfWeekPredicate;
+
+/**
+ * Parses input arguments and creates a new ViewAppointmentsCommand object.
+ * Code is borrowed from existing ViewCommandParser.java due to similarities in desired implementation.
+ */
+public class ViewAppointmentsCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of ViewAppointmentsCommand and returns
+ * a ViewAppointmentsCommand object for execution.
+ * @throws ParseException if the user input does not conform to expected format.
+ */
+ public ViewAppointmentsCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim().toUpperCase();
+ if (trimmedArgs.isEmpty()) {
+
+ // add all days of the week to the list
+ List defaultDayOfWeeks = new ArrayList<>(Appointment.DAY_OF_WEEK_TO_NUM.keySet());
+ return new ViewAppointmentsCommand(new AppointmentIsDayOfWeekPredicate(defaultDayOfWeeks));
+ }
+
+ String[] days = trimmedArgs.split("\\s+");
+ List dayList = Stream.of(days).map(String::toUpperCase).collect(Collectors.toList());
+
+ // check that all day in dayList are valid day of the week
+ for (String day : dayList) {
+ if (!Appointment.DAY_TO_DAY_OF_WEEK.containsKey(day)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewAppointmentsCommand.MESSAGE_USAGE));
+ }
+ }
+
+ List dayOfWeekList = dayList.stream()
+ .map(Appointment.DAY_TO_DAY_OF_WEEK::get)
+ .distinct()
+ .collect(Collectors.toList());
+
+ return new ViewAppointmentsCommand(new AppointmentIsDayOfWeekPredicate(dayOfWeekList));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
new file mode 100644
index 00000000000..477dac9816f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ViewCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ViewCommand object.
+ *
+ * Code is heavily borrowed from existing DeleteCommandParser.java due to similarities in desired implementation.
+ */
+public class ViewCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of ViewCommand and returns
+ * a ViewCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform to expected format.
+ */
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE),
+ pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..60372d84c9c 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -2,10 +2,14 @@
import static java.util.Objects.requireNonNull;
+import java.util.Collection;
import java.util.List;
+import java.util.stream.Collectors;
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.DisjointAppointmentList;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
@@ -15,6 +19,7 @@
*/
public class AddressBook implements ReadOnlyAddressBook {
+ private final DisjointAppointmentList appointments;
private final UniquePersonList persons;
/*
@@ -26,6 +31,7 @@ public class AddressBook implements ReadOnlyAddressBook {
*/
{
persons = new UniquePersonList();
+ appointments = new DisjointAppointmentList();
}
public AddressBook() {}
@@ -43,9 +49,17 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) {
/**
* Replaces the contents of the person list with {@code persons}.
* {@code persons} must not contain duplicate persons.
+ *
*/
public void setPersons(List persons) {
this.persons.setPersons(persons);
+ this.appointments.setAppointments(persons
+ .stream()
+ .flatMap(person -> person.getAppointments()
+ .asUnmodifiableObservableList()
+ .stream())
+ .collect(Collectors.toList()));
+ this.appointments.sort();
}
/**
@@ -53,8 +67,8 @@ public void setPersons(List persons) {
*/
public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
-
setPersons(newData.getPersonList());
+ this.appointments.sort();
}
//// person-level operations
@@ -67,12 +81,24 @@ public boolean hasPerson(Person person) {
return persons.contains(person);
}
+ /**
+ * Returns a list of persons with similar name as {@code person}.
+ */
+ public List findNearDuplicates(Person person) {
+ requireNonNull(person);
+ return persons.findNearDuplicates(person);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
*/
public void addPerson(Person p) {
persons.add(p);
+ for (Appointment appointment : p.getAppointments()) {
+ addAppointment(appointment);
+ }
+ this.appointments.sort();
}
/**
@@ -84,6 +110,17 @@ public void setPerson(Person target, Person editedPerson) {
requireNonNull(editedPerson);
persons.setPerson(target, editedPerson);
+
+ // remove target's appointments
+ for (Appointment appointment : target.getAppointments()) {
+ appointments.remove(appointment);
+ }
+
+ // add editedPerson's appointments
+ for (Appointment appointment : editedPerson.getAppointments()) {
+ addAppointment(appointment);
+ }
+ this.appointments.sort();
}
/**
@@ -92,6 +129,56 @@ public void setPerson(Person target, Person editedPerson) {
*/
public void removePerson(Person key) {
persons.remove(key);
+
+ // remove key's appointments
+ for (Appointment appointment : key.getAppointments()) {
+ appointments.remove(appointment);
+ }
+
+ this.appointments.sort();
+ }
+
+ //// appointment-level operations
+ /**
+ * Adds an appointment to the address book.
+ * The appointment must not overlap with existing appointments in the address book.
+ */
+ public void addAppointment(Appointment appointment) {
+ appointments.add(appointment);
+ this.appointments.sort();
+ }
+
+ /**
+ * Replaces the given appointment {@code target} in the list with {@code editedAppointment}.
+ * {@code target} must exist in the address book.
+ * The appointment {@code editedAppointment} must not overlap with other existing appointments in the address book.
+ */
+ public void setAppointment(Appointment target, Appointment editedAppointment) {
+ requireNonNull(editedAppointment);
+
+ appointments.setAppointment(target, editedAppointment);
+ this.appointments.sort();
+ }
+
+ /**
+ * Returns true if an appointment {@code appointment} overlaps with existing appointments in the address book.
+ */
+ public boolean appointmentsOverlap(Appointment appointment) {
+ requireNonNull(appointment);
+ return appointments.overlaps(appointment);
+ }
+
+ /**
+ * Returns true if an appointment in {@code appointments} overlaps with existing appointments in the address book.
+ */
+ public boolean appointmentsOverlap(Collection appointments) {
+ requireNonNull(appointments);
+ for (Appointment ap : appointments) {
+ if (this.appointments.overlaps(ap)) {
+ return true;
+ }
+ }
+ return false;
}
//// util methods
@@ -108,6 +195,11 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ @Override
+ public ObservableList getAppointmentList() {
+ return appointments.asUnmodifiableObservableList();
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -120,7 +212,9 @@ public boolean equals(Object other) {
}
AddressBook otherAddressBook = (AddressBook) other;
- return persons.equals(otherAddressBook.persons);
+ boolean isPersonsEqual = persons.equals(otherAddressBook.persons);
+ boolean isAppointmentsEqual = appointments.equals(otherAddressBook.appointments);
+ return isPersonsEqual && isAppointmentsEqual;
}
@Override
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..64436c8646e 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,10 +1,13 @@
package seedu.address.model;
import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.appointment.Appointment;
import seedu.address.model.person.Person;
/**
@@ -57,6 +60,12 @@ public interface Model {
*/
boolean hasPerson(Person person);
+ /**
+ * Returns a list of persons with similar name as {@code person}.
+ */
+ List findNearDuplicates(Person person);
+
+
/**
* Deletes the given person.
* The person must exist in the address book.
@@ -76,12 +85,31 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
+ /**
+ * Returns true if an existing appointment overlaps with any appointment in {@code appointments}.
+ */
+ boolean appointmentsOverlap(Collection appointments);
+
+ /**
+ * Returns true if an existing appointment overlaps with {@code appointment}.
+ */
+ boolean appointmentsOverlap(Appointment appointment);
+
/** Returns an unmodifiable view of the filtered person list */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered appointment list */
+ ObservableList getFilteredAppointmentList();
+
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered Appointment list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredAppointmentList(Predicate predicate);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..8fcaac5caff 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,6 +4,8 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
@@ -11,6 +13,7 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.appointment.Appointment;
import seedu.address.model.person.Person;
/**
@@ -22,6 +25,7 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final FilteredList filteredAppointments;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -34,6 +38,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredAppointments = new FilteredList<>(this.addressBook.getAppointmentList());
}
public ModelManager() {
@@ -76,7 +81,6 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
}
//=========== AddressBook ================================================================================
-
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
@@ -87,12 +91,22 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ //// person functionality
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
return addressBook.hasPerson(person);
}
+ /**
+ * Returns a list of persons with similar name as {@code person}.
+ */
+ @Override
+ public List findNearDuplicates(Person person) {
+ requireNonNull(person);
+ return addressBook.findNearDuplicates(person);
+ }
+
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
@@ -101,6 +115,7 @@ public void deletePerson(Person target) {
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
+
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
@@ -111,6 +126,20 @@ public void setPerson(Person target, Person editedPerson) {
addressBook.setPerson(target, editedPerson);
}
+ //// appointment functionality
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ requireNonNull(appointment);
+ return addressBook.appointmentsOverlap(appointment);
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ requireNonNull(appointments);
+ return addressBook.appointmentsOverlap(appointments);
+ }
+
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -128,6 +157,23 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ //=========== Filtered Appointment List Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Appointment} backed by the internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getFilteredAppointmentList() {
+ return filteredAppointments;
+ }
+
+ @Override
+ public void updateFilteredAppointmentList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredAppointments.setPredicate(predicate);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -142,7 +188,8 @@ public boolean equals(Object other) {
ModelManager otherModelManager = (ModelManager) other;
return addressBook.equals(otherModelManager.addressBook)
&& userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
+ && filteredPersons.equals(otherModelManager.filteredPersons)
+ && 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 6ddc2cd9a29..a4304f2778b 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.Person;
/**
@@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook {
*/
ObservableList getPersonList();
+ /**
+ * Returns an unmodifiable view of the appointments list.
+ * This list will not contain any overlapping appointments.
+ */
+ ObservableList getAppointmentList();
+
}
diff --git a/src/main/java/seedu/address/model/appointment/Appointment.java b/src/main/java/seedu/address/model/appointment/Appointment.java
new file mode 100644
index 00000000000..e80ba9d637c
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/Appointment.java
@@ -0,0 +1,209 @@
+package seedu.address.model.appointment;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.DayOfWeek;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Represents a Person's appointment in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidAppointment(String)}
+ */
+public class Appointment implements Comparable {
+ public static final String MESSAGE_CONSTRAINTS = "Appointment should be of the format 'HH:MM-HH:MM DAY' "
+ + "and adhere to the following constraints:\n"
+ + "1. HH:MM follows 24 hour time; "
+ + "HH is from 00 to 23, "
+ + "MM is from 00 to 59.\n"
+ + "2. This is followed by a DAY. "
+ + "DAY must be one of: 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT','SUN'\n";
+ public static final HashMap DAY_TO_DAY_OF_WEEK;
+ public static final HashMap DAY_OF_WEEK_TO_NUM;
+ // alphanumeric and special characters
+ private static final String HOUR = "[\\d]{2}";
+ private static final String MINUTE = "[\\d]{2}";
+ private static final String START_TIME = HOUR + ":" + MINUTE;
+ private static final String END_TIME = HOUR + ":" + MINUTE;
+ private static final String DAY = "[A-z]{3}";
+ public static final String VALIDATION_REGEX = START_TIME + "-" + END_TIME + "[\\s]+" + DAY;
+
+ // initialize map from String to DayOfWeek
+ static {
+ DAY_TO_DAY_OF_WEEK = new HashMap<>();
+ DAY_TO_DAY_OF_WEEK.put("MON", DayOfWeek.MONDAY);
+ DAY_TO_DAY_OF_WEEK.put("TUE", DayOfWeek.TUESDAY);
+ DAY_TO_DAY_OF_WEEK.put("WED", DayOfWeek.WEDNESDAY);
+ DAY_TO_DAY_OF_WEEK.put("THU", DayOfWeek.THURSDAY);
+ DAY_TO_DAY_OF_WEEK.put("FRI", DayOfWeek.FRIDAY);
+ DAY_TO_DAY_OF_WEEK.put("SAT", DayOfWeek.SATURDAY);
+ DAY_TO_DAY_OF_WEEK.put("SUN", DayOfWeek.SUNDAY);
+
+ DAY_OF_WEEK_TO_NUM = new HashMap<>();
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.MONDAY, 1);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.TUESDAY, 2);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.WEDNESDAY, 3);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.THURSDAY, 4);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.FRIDAY, 5);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.SATURDAY, 6);
+ DAY_OF_WEEK_TO_NUM.put(DayOfWeek.SUNDAY, 7);
+ }
+
+ public final String value;
+ private final LocalTime startTime;
+ private final LocalTime endTime;
+ private final DayOfWeek day;
+
+ /**
+ * Constructs a {@code Appointment}.
+ *
+ * @param appointment A valid appointment.
+ */
+ public Appointment(String appointment) {
+ requireNonNull(appointment);
+ checkArgument(isValidAppointment(appointment), MESSAGE_CONSTRAINTS);
+
+ value = appointment.toUpperCase();
+ startTime = LocalTime.parse(extractStartTime(appointment));
+ endTime = LocalTime.parse(extractEndTime(appointment));
+ day = DAY_TO_DAY_OF_WEEK.get(extractDay(appointment));
+ }
+
+ /**
+ * Returns true if a given collection of appointments overlap.
+ */
+ public static boolean hasOverlapping(Collection appointments) {
+ List appointmentList = new ArrayList<>(appointments);
+ int size = appointmentList.size();
+ for (int i = 0; i < size - 1; i += 1) {
+ for (int j = i + 1; j < size; j += 1) {
+ Appointment appointment = appointmentList.get(i);
+ Appointment other = appointmentList.get(j);
+ if (appointment.overlapsWith(other)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if a given string is an appointment.
+ */
+ public static boolean isValidAppointment(String test) {
+ if (!(test.matches(VALIDATION_REGEX))) {
+ return false;
+ }
+
+ boolean isStartTimeValid = isValidTime(extractStartTime(test));
+ boolean isEndTimeValid = isValidTime(extractEndTime(test));
+
+ if (!isStartTimeValid || !isEndTimeValid) {
+ return false;
+ }
+
+ LocalTime startTime = LocalTime.parse(extractStartTime(test));
+ LocalTime endTime = LocalTime.parse(extractEndTime(test));
+ boolean isStartTimeBeforeEndTime = startTime.isBefore(endTime);
+
+ String day = extractDay(test);
+ boolean isDayValid = DAY_TO_DAY_OF_WEEK.containsKey(day);
+
+ return isStartTimeBeforeEndTime && isDayValid;
+ }
+
+ private static boolean isValidTime(String appointment) {
+ int hour = Integer.parseInt(appointment.substring(0, 2));
+ int minute = Integer.parseInt(appointment.substring(3, 5));
+
+ assert(hour > -1);
+ assert(minute > -1);
+ boolean hourValid = hour < 24;
+ boolean minuteValid = minute < 60;
+
+ return hourValid && minuteValid;
+ }
+
+ private static String extractStartTime(String appointment) {
+ return appointment.substring(0, 5);
+ }
+
+ private static String extractEndTime(String appointment) {
+ return appointment.substring(6, 11);
+ }
+
+ private static String extractDay(String appointment) {
+ return appointment.substring(12).trim().toUpperCase();
+ }
+
+ public LocalTime getStartTime() {
+ return startTime;
+ }
+
+ public LocalTime getEndTime() {
+ return endTime;
+ }
+
+ public DayOfWeek getDay() {
+ return day;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Appointment)) {
+ return false;
+ }
+
+ Appointment otherAppointment = (Appointment) other;
+ return value.equals(otherAppointment.value);
+ }
+
+ /**
+ * Return true if appointment overlaps with other, otherwise False
+ */
+ public boolean overlapsWith(Appointment other) {
+ // days are different
+ if (this.day != other.day) {
+ return false;
+ }
+
+ // intervals overlap
+ if (this.startTime.isBefore(other.endTime) && other.startTime.isBefore(this.endTime)) {
+ return true;
+ } else if (other.startTime.isBefore(this.endTime) && this.startTime.isBefore(other.endTime)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(Appointment o) {
+ if (DAY_OF_WEEK_TO_NUM.get(this.day) < DAY_OF_WEEK_TO_NUM.get(o.day)) {
+ return -1;
+ } else if (DAY_OF_WEEK_TO_NUM.get(this.day) > DAY_OF_WEEK_TO_NUM.get(o.day)) {
+ return 1;
+ } else {
+ return this.startTime.compareTo(o.startTime);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicate.java b/src/main/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicate.java
new file mode 100644
index 00000000000..d004a3093f3
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicate.java
@@ -0,0 +1,43 @@
+package seedu.address.model.appointment;
+
+import java.time.DayOfWeek;
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class AppointmentIsDayOfWeekPredicate implements Predicate {
+ private final List days;
+
+ public AppointmentIsDayOfWeekPredicate(List dayOfWeeks) {
+ this.days = dayOfWeeks;
+ }
+
+ @Override
+ public boolean test(Appointment appointment) {
+ return days.stream().anyMatch(day -> day.equals(appointment.getDay()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof seedu.address.model.appointment.AppointmentIsDayOfWeekPredicate)) {
+ return false;
+ }
+
+ AppointmentIsDayOfWeekPredicate otherPredicate = (AppointmentIsDayOfWeekPredicate) other;
+ return this.days.equals(otherPredicate.days);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("days", days).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/appointment/AppointmentList.java b/src/main/java/seedu/address/model/appointment/AppointmentList.java
new file mode 100644
index 00000000000..4d5897e3a39
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/AppointmentList.java
@@ -0,0 +1,145 @@
+package seedu.address.model.appointment;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.appointment.exceptions.AppointmentNotFoundException;
+
+/**
+ * A list of appointments that does not allow nulls.
+ * Supports a minimal set of list operations.
+ */
+public class AppointmentList implements Iterable {
+ protected final ObservableList internalList = FXCollections.observableArrayList();
+ protected final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Adds an appointment to the list.
+ * The appointment must not already exist in the list.
+ */
+ public void add(Appointment toAdd) {
+ requireNonNull(toAdd);
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the appointment {@code target} in the list with {@code editedAppointment}.
+ * {@code target} must exist in the appointment list.
+ */
+ public void setAppointment(Appointment target, Appointment editedAppointment) {
+ requireAllNonNull(target, editedAppointment);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new AppointmentNotFoundException();
+ }
+
+ internalList.set(index, editedAppointment);
+ }
+
+ /**
+ * Removes the equivalent appointment from the list.
+ * The appointment must exist in the list.
+ */
+ public void remove(Appointment toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new AppointmentNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the contents of this list with {@code appointments}.
+ */
+ public void setAppointments(AppointmentList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code appointments}.
+ */
+ public void setAppointments(Collection appointments) {
+ requireAllNonNull(appointments);
+ internalList.setAll(appointments);
+ }
+
+ /**
+ * Sorts the appointment list by the appointment's natural comparator.
+ */
+ public void sort() {
+ internalList.sort(null);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AppointmentList)) {
+ return false;
+ }
+
+ AppointmentList otherAppointmentList = (AppointmentList) other;
+ return internalList.equals(otherAppointmentList.internalList);
+ }
+
+ @Override
+ public String toString() {
+ List appointmentStrings = internalList.stream()
+ .map(Appointment::toString).collect(Collectors.toList());
+ return String.join(", ", appointmentStrings);
+
+ }
+
+ /**
+ * Returns true if the list contains an equivalent appointment as the given argument.
+ */
+ public boolean contains(Appointment toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::equals);
+ }
+
+ /**
+ * Returns true if the list has overlap between appointments.
+ */
+ public boolean isOverlapping() {
+ return Appointment.hasOverlapping(internalList);
+ }
+
+ /**
+ * Add all appointments in {@code appointments} to the list.
+ */
+ public void addAll(Collection appointments) {
+ internalList.addAll(appointments);
+ }
+
+ /**
+ * Returns true if there are no appointments in the list.
+ */
+ public boolean isEmpty() {
+ return internalList.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+}
diff --git a/src/main/java/seedu/address/model/appointment/DisjointAppointmentList.java b/src/main/java/seedu/address/model/appointment/DisjointAppointmentList.java
new file mode 100644
index 00000000000..7975e0a4e1a
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/DisjointAppointmentList.java
@@ -0,0 +1,124 @@
+package seedu.address.model.appointment;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import seedu.address.model.appointment.exceptions.AppointmentNotFoundException;
+import seedu.address.model.appointment.exceptions.OverlappingAppointmentException;
+
+/**
+ * A list of appointments that enforces no overlapping between its elements and does not allow nulls.
+ * Supports a minimal set of list operations.
+ */
+public class DisjointAppointmentList extends AppointmentList {
+ public static final String MESSAGE_CONSTRAINTS =
+ "This person's appointments clash with an existing appointment";
+
+ /**
+ * Returns true if the list contains an appointment overlapping wth the given argument.
+ */
+ public boolean overlaps(Appointment toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::overlapsWith);
+ }
+
+ @Override
+ public boolean isOverlapping() {
+ return false;
+ }
+
+ /**
+ * Adds an appointment {@code toAdd} to the list.
+ * This appointment must not overlap with existing appointments in the list.
+ */
+ @Override
+ public void add(Appointment toAdd) {
+ requireNonNull(toAdd);
+ if (overlaps(toAdd)) {
+ throw new OverlappingAppointmentException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the appointment {@code target} in the list with {@code editedAppointment}.
+ * {@code target} must exist in the appointment list.
+ * {@code editedAppointment} must not overlap with another existing appointment in the list.
+ */
+ @Override
+ public void setAppointment(Appointment target, Appointment editedAppointment) {
+ requireAllNonNull(target, editedAppointment);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new AppointmentNotFoundException();
+ }
+
+ if (overlaps(editedAppointment)) {
+ throw new OverlappingAppointmentException();
+ }
+
+ internalList.set(index, editedAppointment);
+ }
+
+ /**
+ * Removes the equivalent appointment from the list.
+ * The appointment must exist in the list.
+ */
+ public void remove(Appointment toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new AppointmentNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the contents of this list with {@code appointments}.
+ */
+ public void setAppointments(DisjointAppointmentList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code appointments}.
+ * {@code appointments} must not contain overlapping appointments.
+ */
+ @Override
+ public void setAppointments(Collection appointments) {
+ requireAllNonNull(appointments);
+ if (Appointment.hasOverlapping(appointments)) {
+ throw new OverlappingAppointmentException();
+ }
+
+ internalList.setAll(appointments);
+ }
+
+ /**
+ * The list {@code appointments} must not have any appointments that overlap with existing appointments
+ * and also overlap with each other.
+ */
+ public void addAll(Collection appointments) {
+ if (Appointment.hasOverlapping(appointments)) {
+ throw new OverlappingAppointmentException();
+ }
+ for (Appointment ap : appointments) {
+ if (this.overlaps(ap)) {
+ throw new OverlappingAppointmentException();
+ }
+ }
+ internalList.addAll(appointments);
+ }
+
+ public boolean isEmpty() {
+ return internalList.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+}
diff --git a/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java
new file mode 100644
index 00000000000..7181e7c5171
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.appointment.exceptions;
+
+/**
+ * Signals that the operation will result in overlapping appointment intervals.
+ */
+public class AppointmentNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/appointment/exceptions/OverlappingAppointmentException.java b/src/main/java/seedu/address/model/appointment/exceptions/OverlappingAppointmentException.java
new file mode 100644
index 00000000000..1f527830d66
--- /dev/null
+++ b/src/main/java/seedu/address/model/appointment/exceptions/OverlappingAppointmentException.java
@@ -0,0 +1,12 @@
+package seedu.address.model.appointment.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified appointment.
+ */
+public class OverlappingAppointmentException extends RuntimeException {
+ public OverlappingAppointmentException() {
+ super("Operation would result in overlapping appointments");
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 469a2cc9a1e..5d67806c11e 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -30,6 +30,10 @@ public Address(String address) {
value = address;
}
+ protected Address() {
+ value = null;
+ }
+
/**
* Returns true if a given string is a valid email.
*/
@@ -53,6 +57,10 @@ public boolean equals(Object other) {
return false;
}
+ if (other instanceof EmptyAddress) {
+ return false;
+ }
+
Address otherAddress = (Address) other;
return value.equals(otherAddress.value);
}
@@ -62,4 +70,7 @@ public int hashCode() {
return value.hashCode();
}
+ public boolean isEmpty() {
+ return false;
+ }
}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index c62e512bc29..b7e90cacb94 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -43,6 +43,9 @@ public Email(String email) {
checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
value = email;
}
+ protected Email() {
+ value = null;
+ }
/**
* Returns if a given string is a valid email.
@@ -67,6 +70,10 @@ public boolean equals(Object other) {
return false;
}
+ if (other instanceof EmptyEmail) {
+ return false;
+ }
+
Email otherEmail = (Email) other;
return value.equals(otherEmail.value);
}
@@ -76,4 +83,7 @@ public int hashCode() {
return value.hashCode();
}
+ public boolean isEmpty() {
+ return false;
+ }
}
diff --git a/src/main/java/seedu/address/model/person/EmptyAddress.java b/src/main/java/seedu/address/model/person/EmptyAddress.java
new file mode 100644
index 00000000000..66328e448a5
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmptyAddress.java
@@ -0,0 +1,28 @@
+package seedu.address.model.person;
+
+/**
+ * An Empty Address class, for use when no field is listed in a person's address during creation
+ */
+public class EmptyAddress extends Address {
+ public EmptyAddress() {
+ super();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (other instanceof EmptyAddress) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/EmptyEmail.java b/src/main/java/seedu/address/model/person/EmptyEmail.java
new file mode 100644
index 00000000000..48cffbd8141
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmptyEmail.java
@@ -0,0 +1,28 @@
+package seedu.address.model.person;
+
+/**
+ * An Empty Email class, for use when no field is listed in a person's email during creation
+ */
+public class EmptyEmail extends Email {
+ public EmptyEmail() {
+ super();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (other instanceof EmptyEmail) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/EmptyLevel.java b/src/main/java/seedu/address/model/person/EmptyLevel.java
new file mode 100644
index 00000000000..28659cbdcfc
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmptyLevel.java
@@ -0,0 +1,33 @@
+package seedu.address.model.person;
+
+/**
+ * An Empty Level class, for use when no field is listed in a person's level during creation
+ */
+public class EmptyLevel extends Level {
+ public EmptyLevel() {
+ super();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (other instanceof EmptyLevel) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/EmptyNote.java b/src/main/java/seedu/address/model/person/EmptyNote.java
new file mode 100644
index 00000000000..11ef44909fc
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmptyNote.java
@@ -0,0 +1,28 @@
+package seedu.address.model.person;
+
+/**
+ * An Empty Note class, for use when no field is listed in a person's note during creation
+ */
+public class EmptyNote extends Note {
+ public EmptyNote() {
+ super();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (other instanceof EmptyNote) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/EmptyPhone.java b/src/main/java/seedu/address/model/person/EmptyPhone.java
new file mode 100644
index 00000000000..9ecd7779fc7
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/EmptyPhone.java
@@ -0,0 +1,28 @@
+package seedu.address.model.person;
+
+/**
+ * An Empty Phone class, for use when no field is listed in a person's phone during creation
+ */
+public class EmptyPhone extends Phone {
+ public EmptyPhone() {
+ super();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (other instanceof EmptyPhone) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Level.java b/src/main/java/seedu/address/model/person/Level.java
new file mode 100644
index 00000000000..45f336c4d15
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Level.java
@@ -0,0 +1,65 @@
+package seedu.address.model.person;
+
+/**
+ * Represents a Person's level in the address book.
+ * Guarantees: immutable; is always valid
+ */
+public class Level {
+ public static final String MESSAGE_CONSTRAINTS = "Levels should only be P1, P2, P3, P4, P5 or P6";
+
+ private final LevelEnum internalLevel;
+
+ /**
+ * Constructs a {@code Level}.
+ *
+ * @param level A valid level.
+ */
+ public Level(String level) {
+ level = level.trim().toUpperCase();
+ this.internalLevel = LevelEnum.valueOf(level);
+ }
+
+ public Level() {
+ this.internalLevel = null;
+ }
+
+ @Override
+ public String toString() {
+ return internalLevel.toString();
+ }
+
+ /**
+ * Returns if a given string is a valid level.
+ */
+ public static boolean isValidLevel(String test) {
+ try {
+ test = test.trim().toUpperCase();
+ LevelEnum.valueOf(test);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Level)) {
+ return false;
+ }
+
+ if (other instanceof EmptyLevel) {
+ return false;
+ }
+
+ Level otherLevel = (Level) other;
+ return internalLevel.equals(otherLevel.internalLevel);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/LevelEnum.java b/src/main/java/seedu/address/model/person/LevelEnum.java
new file mode 100644
index 00000000000..1deed3918fb
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/LevelEnum.java
@@ -0,0 +1,5 @@
+package seedu.address.model.person;
+
+enum LevelEnum {
+ P1, P2, P3, P4, P5, P6
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..20d52c81b9d 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -56,7 +56,9 @@ public boolean equals(Object other) {
}
Name otherName = (Name) other;
- return fullName.equals(otherName.fullName);
+ String fullNameLower = fullName.toLowerCase();
+ String otherNameLower = otherName.fullName.toLowerCase();
+ return fullNameLower.equals(otherNameLower);
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Note.java b/src/main/java/seedu/address/model/person/Note.java
new file mode 100644
index 00000000000..5b1e84c10ce
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Note.java
@@ -0,0 +1,58 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Person's note in the address book.
+ * Guarantees: immutable; is always valid
+ */
+public class Note {
+ public final String value;
+
+ /**
+ * Constructs an {@code Note}.
+ *
+ * @param note A valid note.
+ */
+ public Note(String note) {
+ requireNonNull(note);
+ value = note;
+ }
+
+ protected Note() {
+ value = null;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Note)) {
+ return false;
+ }
+
+ if (other instanceof EmptyNote) {
+ return false;
+ }
+
+ Note otherNote = (Note) other;
+ return value.equals(otherNote.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..95bc96a37ad 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -2,12 +2,17 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
+import seedu.address.commons.util.StringUtil;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.tag.Tag;
/**
@@ -20,21 +25,32 @@ public class Person {
private final Name name;
private final Phone phone;
private final Email email;
+ private final Note note;
// Data fields
private final Address address;
private final Set tags = new HashSet<>();
+ private final AppointmentList appointments;
+ private final Set subjects = new HashSet<>();
+ private final Level level;
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(
+ Name name, Phone phone, Email email, Address address, Note note,
+ Set tags, AppointmentList appointments, Set subjects, Level level
+ ) {
+ requireAllNonNull(name, phone, email, address, tags, appointments, subjects, level);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.note = note;
this.tags.addAll(tags);
+ this.appointments = appointments;
+ this.subjects.addAll(subjects);
+ this.level = level;
}
public Name getName() {
@@ -53,6 +69,14 @@ public Address getAddress() {
return address;
}
+ public Note getNote() {
+ return note;
+ }
+
+ public Level getLevel() {
+ return level;
+ }
+
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
@@ -61,6 +85,84 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ public AppointmentList getAppointments() {
+ return appointments;
+ }
+
+ /**
+ * Returns an immutable subject set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getSubjects() {
+ return Collections.unmodifiableSet(subjects);
+ }
+
+ /**
+ * Returns a formatted string that contains all the details of the Person object for the
+ * View command.
+ */
+ public List getViewDetails() {
+ List detailList = new ArrayList<>();
+
+ assert this.getName() != null;
+ detailList.add(this.getName().fullName.toUpperCase() + "\n");
+ detailList.add(getSummary());
+ detailList.add(StringUtil.SEPARATOR);
+
+ detailList.add("\nDETAILS:\n");
+ detailList.add(this.getPhone().isEmpty() ? "-" : this.getPhone().value + "\n");
+ detailList.add(this.getEmail().isEmpty() ? "-" : this.getEmail().value + "\n");
+ detailList.add(this.getAddress().isEmpty() ? "-" : this.getAddress().value + "\n");
+ detailList.add(StringUtil.SEPARATOR);
+
+ detailList.add("\nAPPOINTMENTS:\n");
+ detailList.add(this.getAppointments().isEmpty()
+ ? "-\n"
+ : this.getAppointments()
+ .asUnmodifiableObservableList()
+ .stream()
+ .map(Object::toString)
+ .map(str -> str + "\n")
+ .collect(Collectors.joining()));
+ detailList.add(StringUtil.SEPARATOR);
+
+ detailList.add("\nNOTES:\n" + (this.getNote().isEmpty() ? "-" : this.getNote().value));
+
+ return detailList;
+ }
+
+ /**
+ * Returns a string containing the level, subject and tags of the person.
+ */
+ public String getSummary() {
+ String summaryString = "\n";
+ summaryString += this.getLevel().isEmpty()
+ ? ""
+ : "[" + this.getLevel().toString() + "] ";
+ summaryString += this.getSubjects().isEmpty()
+ ? ""
+ : this.getSubjects().stream()
+ .map(Object::toString)
+ .map(str -> str + " ")
+ .collect(Collectors.joining());
+ summaryString += this.getTags().isEmpty()
+ ? "\n"
+ : this.getTags().stream()
+ .map(Object::toString)
+ .map(str -> str + " ")
+ .collect(Collectors.joining())
+ .trim() + "\n";
+
+ return summaryString;
+ }
+
+ /**
+ * Returns a boolean value which indicates whether the person has any appointments.
+ */
+ public boolean hasAppointments() {
+ return !appointments.isEmpty();
+ }
+
/**
* Returns true if both persons have the same name.
* This defines a weaker notion of equality between two persons.
@@ -94,24 +196,42 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ && note.equals(otherPerson.note)
+ && tags.equals(otherPerson.tags)
+ && appointments.equals(otherPerson.appointments)
+ && subjects.equals(otherPerson.subjects)
+ && level.equals(otherPerson.level);
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, tags, appointments);
}
@Override
public String toString() {
- return new ToStringBuilder(this)
- .add("name", name)
- .add("phone", phone)
- .add("email", email)
- .add("address", address)
- .add("tags", tags)
- .toString();
- }
+ ToStringBuilder returnedString = new ToStringBuilder(this)
+ .add("name", name);
+ if (!phone.isEmpty()) {
+ returnedString.add("phone", phone);
+ }
+ if (!email.isEmpty()) {
+ returnedString.add("email", email);
+ }
+ if (!address.isEmpty()) {
+ returnedString.add("address", address);
+ }
+ if (!note.isEmpty()) {
+ returnedString.add("note", note);
+ }
+ returnedString.add("tags", tags);
+ returnedString.add("appointments", appointments);
+ returnedString.add("subjects", subjects);
+ if (!level.isEmpty()) {
+ returnedString.add("level", level);
+ }
+ return returnedString.toString();
+ }
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index d733f63d739..ed28f370ca9 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -26,6 +26,10 @@ public Phone(String phone) {
value = phone;
}
+ protected Phone() {
+ value = null;
+ }
+
/**
* Returns true if a given string is a valid phone number.
*/
@@ -49,6 +53,10 @@ public boolean equals(Object other) {
return false;
}
+ if (other instanceof EmptyPhone) {
+ return false;
+ }
+
Phone otherPhone = (Phone) other;
return value.equals(otherPhone.value);
}
@@ -58,4 +66,7 @@ public int hashCode() {
return value.hashCode();
}
+ public boolean isEmpty() {
+ return false;
+ }
}
diff --git a/src/main/java/seedu/address/model/person/Subject.java b/src/main/java/seedu/address/model/person/Subject.java
new file mode 100644
index 00000000000..098e17d0100
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Subject.java
@@ -0,0 +1,64 @@
+package seedu.address.model.person;
+
+/**
+ * Represents a Person's subject in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)}
+ */
+public class Subject {
+
+ public static final String MESSAGE_CONSTRAINTS = "Subjects should only be ENGLISH, MATH, SCIENCE or MT.";
+ private final SubjectEnum internalSubject;
+
+ /**
+ * Constructs a {@code Subject}.
+ *
+ * @param subject A valid subject.
+ */
+ public Subject(String subject) {
+ subject = subject.trim().toUpperCase();
+ this.internalSubject = SubjectEnum.valueOf(subject);
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return '[' + internalSubject.toString() + ']';
+ }
+
+ public String getSubject() {
+ return internalSubject.toString();
+ }
+
+ /**
+ * Returns true if a given string is a valid subject.
+ */
+ public static boolean isValidSubject(String test) {
+ try {
+ test = test.trim().toUpperCase();
+ SubjectEnum.valueOf(test);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return internalSubject.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Subject)) {
+ return false;
+ }
+
+ Subject otherSubject = (Subject) other;
+ return internalSubject.equals(otherSubject.internalSubject);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/SubjectEnum.java b/src/main/java/seedu/address/model/person/SubjectEnum.java
new file mode 100644
index 00000000000..048706ac145
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/SubjectEnum.java
@@ -0,0 +1,5 @@
+package seedu.address.model.person;
+
+enum SubjectEnum {
+ ENGLISH, MATH, SCIENCE, MT
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index cc0a68d79f9..bf930ccd469 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -3,8 +3,10 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.stream.Collectors;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -36,6 +38,41 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns list of names similar to the given person's name.
+ */
+ public List findNearDuplicates(Person toCheck) {
+ requireNonNull(toCheck);
+ String toCheckName = normalizeName(toCheck.getName().toString());
+
+ List nearDuplicates = new ArrayList<>();
+ for (String name : getAllNames()) {
+ String normalizedCurrentName = normalizeName(name);
+ if (toCheckName.equals(normalizedCurrentName)) {
+ // Add the "original" duplicate name
+ nearDuplicates.add(name);
+ }
+ }
+ return nearDuplicates;
+ }
+
+ /**
+ * Retrieves the names of all persons in the list.
+ */
+ public List getAllNames() {
+ return internalList.stream()
+ .map(person -> person.getName().toString())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Normalizes names by removing unnecessary whitespaces and lowering the case.
+ */
+ public String normalizeName(String name) {
+ requireNonNull(name);
+ return name.trim().replaceAll("\\s+", "").toLowerCase();
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..6b1bd04273a 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,42 +1,57 @@
package seedu.address.model.util;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
public class SampleDataUtil {
+
+ public static final Note EMPTY_NOTE = new Note("");
+
public static Person[] getSamplePersons() {
return new Person[] {
new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
+ new Address("Blk 30 Geylang Street 29, #06-40"), new Note("Weak in fractions."),
+ getTagSet("referral"), getAppointmentList("12:00-13:00 SUN", "00:00-01:00 MON"),
+ getSubjectSet("Math", "Science"), new Level("P4")),
new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_NOTE,
+ getTagSet("weak"), getAppointmentList("08:00-09:00 MON"),
+ getSubjectSet("Math", "English"), new Level("P2")),
new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), EMPTY_NOTE,
+ getTagSet("payment"), getAppointmentList("14:20-15:00 SAT"),
+ getSubjectSet("Science"), new Level("P3")),
new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), EMPTY_NOTE,
+ new HashSet(), getAppointmentList("16:30-18:00 THU", "19:00-20:00 WED"),
+ getSubjectSet("Math", "Science"), new Level("P6")),
new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
+ new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_NOTE,
+ new HashSet(), getAppointmentList("16:00-17:00 TUE"),
+ getSubjectSet("Math", "Science"), new Level("P6")),
new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
+ new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_NOTE,
+ new HashSet(), getAppointmentList("14:30-15:00 WED"),
+ getSubjectSet("Math", "Science"), new Level("P4"))
};
}
@@ -57,4 +72,24 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ /**
+ * Returns an appointment set containing the list of strings given.
+ */
+ public static AppointmentList getAppointmentList(String... strings) {
+ AppointmentList appointments = new AppointmentList();
+ appointments.addAll(Arrays.stream(strings)
+ .map(Appointment::new)
+ .collect(Collectors.toList()));
+ return appointments;
+ }
+
+ /**
+ * Returns a subject set containing the list of strings given.
+ */
+ public static Set getSubjectSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(Subject::new)
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java
new file mode 100644
index 00000000000..a4cd7408c11
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java
@@ -0,0 +1,49 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Jackson-friendly version of {@link Appointment}.
+ */
+class JsonAdaptedAppointment {
+
+ private final String appointment;
+
+ /**
+ * Constructs a {@code JsonAdaptedAddress} with the given {@code appointment}.
+ */
+ @JsonCreator
+ public JsonAdaptedAppointment(String appointment) {
+ this.appointment = appointment;
+ }
+
+ /**
+ * Converts a given {@code Appointment} into this class for Jackson use.
+ */
+ public JsonAdaptedAppointment(Appointment source) {
+ appointment = source.value;
+ }
+
+ @JsonValue
+ public String getAppointment() {
+ return appointment;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted appointment object into the model's {@code Appointment} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted appointment.
+ */
+ public Appointment toModelType() throws IllegalValueException {
+ if (!Appointment.isValidAppointment(appointment)) {
+ throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
+ }
+ return new Appointment(appointment);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..17eaa351c71 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,11 +10,21 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmptyAddress;
+import seedu.address.model.person.EmptyEmail;
+import seedu.address.model.person.EmptyLevel;
+import seedu.address.model.person.EmptyNote;
+import seedu.address.model.person.EmptyPhone;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
@@ -28,7 +38,11 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final String note;
private final List tags = new ArrayList<>();
+ private final List appointments = new ArrayList<>();
+ private final List subjects = new ArrayList<>();
+ private final String level;
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
@@ -36,14 +50,24 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ @JsonProperty("note") String note, @JsonProperty("tags") List tags,
+ @JsonProperty("appointments") List appointments,
+ @JsonProperty("subjects") List subjects, @JsonProperty("level") String level) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.note = note;
+ this.level = level;
if (tags != null) {
this.tags.addAll(tags);
}
+ if (appointments != null) {
+ this.appointments.addAll(appointments);
+ }
+ if (subjects != null) {
+ this.subjects.addAll(subjects);
+ }
}
/**
@@ -54,9 +78,17 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ note = source.getNote().value;
+ level = source.getLevel().toString();
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ appointments.addAll(source.getAppointments().asUnmodifiableObservableList().stream()
+ .map(JsonAdaptedAppointment::new)
+ .collect(Collectors.toList()));
+ subjects.addAll(source.getSubjects().stream()
+ .map(JsonAdaptedSubject::new)
+ .collect(Collectors.toList()));
}
/**
@@ -66,9 +98,17 @@ public JsonAdaptedPerson(Person source) {
*/
public Person toModelType() throws IllegalValueException {
final List personTags = new ArrayList<>();
+ final List personAppointments = new ArrayList<>();
+ final List personSubjects = new ArrayList<>();
for (JsonAdaptedTag tag : tags) {
personTags.add(tag.toModelType());
}
+ for (JsonAdaptedAppointment appointment : appointments) {
+ personAppointments.add(appointment.toModelType());
+ }
+ for (JsonAdaptedSubject subject : subjects) {
+ personSubjects.add(subject.toModelType());
+ }
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
@@ -78,32 +118,61 @@ public Person toModelType() throws IllegalValueException {
}
final Name modelName = new Name(name);
+ Phone usedPhone;
if (phone == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
- }
- if (!Phone.isValidPhone(phone)) {
+ usedPhone = new EmptyPhone();
+ } else if (!Phone.isValidPhone(phone)) {
throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ } else {
+ usedPhone = new Phone(phone);
}
- final Phone modelPhone = new Phone(phone);
+ final Phone modelPhone = usedPhone;
+ Email usedEmail;
if (email == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
- }
- if (!Email.isValidEmail(email)) {
+ usedEmail = new EmptyEmail();
+ } else if (!Email.isValidEmail(email)) {
throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
+ } else {
+ usedEmail = new Email(email);
}
- final Email modelEmail = new Email(email);
+ final Email modelEmail = usedEmail;
+ Address usedAddress;
if (address == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
- }
- if (!Address.isValidAddress(address)) {
+ usedAddress = new EmptyAddress();
+ } else if (!Address.isValidAddress(address)) {
throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
+ } else {
+ usedAddress = new Address(address);
+ }
+ final Address modelAddress = usedAddress;
+
+ Note usedNote;
+ if (note == null) {
+ usedNote = new EmptyNote();
+ } else {
+ usedNote = new Note(note);
+ }
+ final Note modelNote = usedNote;
+
+ Level usedLevel;
+ if (level == null) {
+ usedLevel = new EmptyLevel();
+ } else {
+ usedLevel = new Level(level);
}
- final Address modelAddress = new Address(address);
+ final Level modelLevel = usedLevel;
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+
+ final AppointmentList modelAppointments = new AppointmentList();
+ modelAppointments.addAll(personAppointments);
+
+ final Set modelSubjects = new HashSet<>(personSubjects);
+
+ return new Person(modelName, modelPhone, modelEmail, modelAddress,
+ modelNote, modelTags, modelAppointments, modelSubjects, modelLevel);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedSubject.java b/src/main/java/seedu/address/storage/JsonAdaptedSubject.java
new file mode 100644
index 00000000000..b974335b118
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedSubject.java
@@ -0,0 +1,49 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Subject;
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * Jackson-friendly version of {@link seedu.address.model.person.Subject}.
+ */
+public class JsonAdaptedSubject {
+
+ private final String subject;
+
+ /**
+ * Constructs a {@code JsonAdaptedSubject} with the given {@code subject}.
+ */
+ @JsonCreator
+ public JsonAdaptedSubject(String subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * Converts a given {@code Subject} into this class for Jackson use.
+ */
+ public JsonAdaptedSubject(Subject source) {
+ subject = source.getSubject();
+ }
+
+ @JsonValue
+ public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted subject object into the model's {@code Subject} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted subject.
+ */
+ public Subject toModelType() throws IllegalValueException {
+ if (!Subject.isValidSubject(subject)) {
+ throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
+ }
+ return new Subject(subject);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..8114d35d977 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -11,6 +11,8 @@
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.appointment.AppointmentList;
import seedu.address.model.person.Person;
/**
@@ -20,15 +22,21 @@
class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_OVERLAPPING_APPOINTMENT =
+ "Appointment list contains overlapping appointment(s).";
+ public static final String MESSAGE_APPOINTMENTS_PERSONS_MISMATCH = "Persons list and appointments list don't match";
private final List persons = new ArrayList<>();
+ private final List appointments = new ArrayList<>();
/**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
+ * Constructs a {@code JsonSerializableAddressBook} with the given persons and appointments.
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
+ public JsonSerializableAddressBook(@JsonProperty("persons") List persons,
+ @JsonProperty("appointments") List appointments) {
this.persons.addAll(persons);
+ this.appointments.addAll(appointments);
}
/**
@@ -37,7 +45,14 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s2-cs2103-f09-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);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..484200116cd 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -34,7 +34,6 @@ public class MainWindow extends UiPart {
private PersonListPanel personListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
-
@FXML
private StackPane commandBoxPlaceholder;
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..79c6b5ca03d 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -8,6 +8,7 @@
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Subject;
/**
* An UI component that displays information of a {@code Person}.
@@ -35,11 +36,14 @@ public class PersonCard extends UiPart {
@FXML
private Label phone;
@FXML
- private Label address;
- @FXML
private Label email;
@FXML
- private FlowPane tags;
+ private FlowPane summary;
+ @FXML
+ private FlowPane subjects;
+ @FXML
+ private FlowPane level;
+
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -49,11 +53,32 @@ public PersonCard(Person person, int displayedIndex) {
this.person = person;
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
- phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
+ phone.setText(person.getPhone().isEmpty() ? "-" : person.getPhone().value);
+ email.setText(person.getEmail().isEmpty() ? "-" : person.getEmail().value);
+
+ String level = person.getLevel().toString();
+ if (level != null) {
+ summary.getChildren().add(createSummaryLabel(level, level));
+ }
+
+ person.getSubjects().stream()
+ .sorted(Comparator.comparing(Subject::getSubject))
+ .forEach(subject -> summary.getChildren()
+ .add(createSummaryLabel(subject.getSubject(), subject.getSubject())));
+
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ .forEach(tag -> summary.getChildren().add(createSummaryLabel(tag.tagName, "tag")));
+
+ }
+
+ /**
+ * Returns a Label object that is used in the summary FlowPane containing the specified text content
+ * and style class.
+ */
+ private Label createSummaryLabel(String content, String classToAdd) {
+ Label customLabel = new Label(content);
+ customLabel.getStyleClass().add(classToAdd);
+ return customLabel;
}
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..6b6a03b9643 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/tutorrec.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/tutorrec.png b/src/main/resources/images/tutorrec.png
new file mode 100644
index 00000000000..ea9cb1a088f
Binary files /dev/null and b/src/main/resources/images/tutorrec.png differ
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..2543f77aff3 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -1,5 +1,5 @@
.background {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #000000;
background-color: #383838; /* Used in the default.html file */
}
@@ -91,6 +91,8 @@
-fx-background-insets: 0;
-fx-padding: 0;
-fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: #A4A4A4;
+ -fx-border-width: 2px;
}
.list-cell {
@@ -100,11 +102,11 @@
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: #4D4D4D;
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
+ -fx-background-color: #2E2E2E;
}
.list-cell:filled:selected {
@@ -133,17 +135,15 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #000000;
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
- -fx-border-top-width: 1px;
+ -fx-background-color: #000000;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #565656;
}
.result-display {
@@ -159,7 +159,7 @@
.status-bar .label {
-fx-font-family: "Segoe UI Light";
- -fx-text-fill: white;
+ -fx-text-fill: #A3A3A3;
-fx-padding: 4px;
-fx-pref-height: 30px;
}
@@ -181,11 +181,11 @@
}
.grid-pane .stack-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #000000;
}
.context-menu {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: #000000;
}
.context-menu .label {
@@ -193,7 +193,10 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #000000;
+ -fx-border-color: #ffffff;
+ -fx-border-width: 1px;
+ -fx-border-style: hidden hidden solid hidden;
}
.menu-bar .label {
@@ -318,11 +321,11 @@
}
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-color: #2E2E2E;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-color: #ffffff;
-fx-border-insets: 0;
- -fx-border-width: 1;
+ -fx-border-width: 1.5;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
@@ -332,21 +335,75 @@
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
+#personListPanel {
+ -fx-border-color: #A4A4A4;
+ -fx-border-width: 2px;
+}
+
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-color: #2E2E2E;
-fx-background-radius: 0;
+ -fx-border-color: #555353;
+ -fx-border-insets: 0;
+ -fx-border-width: 2;
}
-#tags {
- -fx-hgap: 7;
+#summary {
+ -fx-hgap: 5;
-fx-vgap: 3;
+ -fx-padding: 2 0 2 0;
}
-#tags .label {
+#summary .label {
-fx-text-fill: white;
-fx-background-color: #3e7b91;
- -fx-padding: 1 3 1 3;
- -fx-border-radius: 2;
- -fx-background-radius: 2;
- -fx-font-size: 11;
+ -fx-padding: 2 8 2 8;
+ -fx-border-radius: 8;
+ -fx-background-radius: 8;
+ -fx-font-size: 13;
+ -fx-font-weight: bold;
+}
+
+#summary .tag {
+ -fx-background-color: #3e7b91;
+}
+
+#summary .P1 {
+ -fx-background-color: #9340f7;
+}
+
+#summary .P2 {
+ -fx-background-color: #bd40f7;
+}
+
+#summary .P3 {
+ -fx-background-color: #e540f7;
+}
+
+#summary .P4 {
+ -fx-background-color: #f740c7;
+}
+
+#summary .P5 {
+ -fx-background-color: #f74083;
+}
+
+#summary .P6 {
+ -fx-background-color: #f74040;
+}
+
+#summary .ENGLISH {
+ -fx-background-color: #4c49fc;
+}
+
+#summary .MATH {
+ -fx-background-color: #94622c;
+}
+
+#summary .SCIENCE {
+ -fx-background-color: #298028;
+}
+
+#summary .MT {
+ -fx-background-color: #808028;
}
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..520a0f2670b 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -6,15 +6,15 @@
-
+
+
-
+
-
+
@@ -23,7 +23,7 @@
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f5e812e25e6..1f9234ea77e 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -14,7 +14,7 @@
-
+
@@ -27,9 +27,8 @@
-
+
-
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 01b691792a9..6044fe878ee 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -5,5 +5,5 @@
-
+
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..a1a07bf5ca5 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -9,5 +9,6 @@
"phone": "948asdf2424",
"email": "hans@example.com",
"address": "4th street"
- } ]
+ } ],
+ "appointments": [ ]
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..395bc915976 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -4,5 +4,6 @@
"phone": "9482424",
"email": "hans@example.com",
"address": "4th street"
- } ]
+ } ],
+ "appointments": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..3e9494499f3 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,14 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
+ "note": "",
"tags": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
- "address": "4th street"
- } ]
+ "address": "4th street",
+ "note": ""
+ } ],
+ "appointments": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..0faa2a327f7 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -4,5 +4,6 @@
"phone": "9482424",
"email": "invalid@email!3e",
"address": "4th street"
- } ]
+ } ],
+ "appointments": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..ba73adeb1c8 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,65 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
- "tags" : [ "friends" ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ "friends" ],
+ "subjects": [ ],
+ "level": "P1"
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
- "tags" : [ "owesMoney", "friends" ]
+ "note" : "He can't take beer!",
+ "tags" : [ "owesMoney", "friends" ],
+ "appointments" : [ "12:00-13:00 SUN" ],
+ "subjects": [ "MATH" ],
+ "level": "P1"
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
- "tags" : [ ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ ],
+ "subjects": [ ],
+ "level": "P1"
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
- "tags" : [ "friends" ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ "friends" ],
+ "subjects": [ ],
+ "level": "P1"
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
- "tags" : [ ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ ],
+ "subjects": [ ],
+ "level": "P1"
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
- "tags" : [ ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ ],
+ "subjects": [ ],
+ "level": "P1"
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
- "tags" : [ ]
- } ]
+ "note" : "She likes aardvarks.",
+ "tags" : [ ],
+ "subjects": [ ],
+ "level": "P1"
+ } ],
+ "appointments": ["12:00-13:00 SUN"]
}
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..56cc0d167c6 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -6,6 +6,7 @@
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.NOTE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -166,8 +167,8 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
// Triggers the saveAddressBook method by executing an add command
String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY;
+ Person expectedPerson = new PersonBuilder(AMY).withTags().removeLevel().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index 162a0c86031..ffc0ebbe1d7 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -1,7 +1,9 @@
package seedu.address.logic.commands;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.BeforeEach;
@@ -11,6 +13,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.appointment.DisjointAppointmentList;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -45,4 +48,26 @@ public void execute_duplicatePerson_throwsCommandException() {
AddCommand.MESSAGE_DUPLICATE_PERSON);
}
+ @Test
+ public void execute_duplicateAppointments_throwsCommandException() {
+ Person personWithOverlappingAppointments = new PersonBuilder().withName("name").withAppointments(
+ VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_FRIDAY).build();
+ assertCommandFailure(new AddCommand(personWithOverlappingAppointments), model,
+ DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }
+
+ @Test
+ public void execute_overlappingAppointments_throwsCommandException() {
+ Person personWithOverlappingAppointments = new PersonBuilder().withName("name").withAppointments(
+ "10:00-12:00 SUN", "11:00-13:00 SUN").build();
+ assertCommandFailure(new AddCommand(personWithOverlappingAppointments), model,
+ DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }
+
+ @Test
+ public void execute_overlappingAppointmentsWithExistingAppointments_throwsCommandException() {
+ Person personWithOverlappingAppointments = new PersonBuilder(BENSON).withName("notBenson").build();
+ assertCommandFailure(new AddCommand(personWithOverlappingAppointments), model,
+ DisjointAppointmentList.MESSAGE_CONSTRAINTS);
+ }
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..4ce49034406 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -5,11 +5,15 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalAppointments.SUN_APPOINTMENT_10_TO_12;
import static seedu.address.testutil.TypicalPersons.ALICE;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
@@ -22,6 +26,8 @@
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.DisjointAppointmentList;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -44,6 +50,27 @@ public void execute_personAcceptedByModel_addSuccessful() throws Exception {
assertEquals(Arrays.asList(validPerson), modelStub.personsAdded);
}
+ // work on this qy
+ @Test
+ public void execute_nearDuplicatePerson_addNearDuplicateSuccessful() throws Exception {
+ Person nearDuplicate = new PersonBuilder().withName("John Doe").build();
+
+ // Create a Model stub that returns the near duplicate person when findNearDuplicates is called
+ ModelStub modelStub = new ModelStubAcceptingPersonAddedWithNearDuplicate(nearDuplicate);
+
+ // Create the person to be added (similar to the near duplicate)
+ Person personToAdd = new PersonBuilder().withName("john Doe ").build();
+
+ // Execute the AddCommand with the person to be added
+ CommandResult commandResult = new AddCommand(personToAdd).execute(modelStub);
+
+ assertEquals(String.format(AddCommand.MESSAGE_NEAR_DUPLICATES, Messages.format(personToAdd),
+ nearDuplicate.getName().toString()), commandResult.getFeedbackToUser());
+
+ assertTrue(modelStub.hasPerson(personToAdd));
+ }
+
+
@Test
public void execute_duplicatePerson_throwsCommandException() {
Person validPerson = new PersonBuilder().build();
@@ -53,6 +80,17 @@ public void execute_duplicatePerson_throwsCommandException() {
assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub));
}
+
+ @Test
+ public void execute_overlappingAppointment_throwsCommandException() {
+ Person anotherPerson = new PersonBuilder(ALICE).withAppointments("11:00-13:00 SUN").build();
+ AddCommand addCommand = new AddCommand(anotherPerson);
+ ModelStub modelStub = new ModelStubWithAppointment(SUN_APPOINTMENT_10_TO_12);
+
+ assertThrows(CommandException.class,
+ DisjointAppointmentList.MESSAGE_CONSTRAINTS, () -> addCommand.execute(modelStub));
+ }
+
@Test
public void equals() {
Person alice = new PersonBuilder().withName("Alice").build();
@@ -85,7 +123,7 @@ public void toStringMethod() {
}
/**
- * A default model stub that have all of the methods failing.
+ * A default model stub that have all but one method that add depends on failing.
*/
private class ModelStub implements Model {
@Override
@@ -138,6 +176,11 @@ public boolean hasPerson(Person person) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public List findNearDuplicates(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public void deletePerson(Person target) {
throw new AssertionError("This method should not be called.");
@@ -157,6 +200,26 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void updateFilteredAppointmentList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredAppointmentList() {
+ throw new AssertionError("This method should not be called.");
+ }
}
/**
@@ -175,6 +238,50 @@ public boolean hasPerson(Person person) {
requireNonNull(person);
return this.person.isSamePerson(person);
}
+
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ return false;
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ return false;
+ }
+ }
+
+ /**
+ * A Model stub that contains a single appointment.
+ */
+ private class ModelStubWithAppointment extends ModelStub {
+ private final Appointment appointment;
+
+ ModelStubWithAppointment(Appointment appointment) {
+ requireNonNull(appointment);
+ this.appointment = appointment;
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ return false;
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ requireNonNull(appointment);
+ return this.appointment.overlapsWith(appointment);
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ requireNonNull(appointments);
+ for (Appointment ap : appointments) {
+ if (this.appointment.overlapsWith(ap)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
/**
@@ -199,6 +306,66 @@ public void addPerson(Person person) {
public ReadOnlyAddressBook getAddressBook() {
return new AddressBook();
}
+
+ @Override
+ public List findNearDuplicates(Person person) {
+ // Stub implementation, return an empty list
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ return false;
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ return false;
+ }
+ }
+
+ /**
+ * A Model stub that accepts the person added and returns a near duplicate.
+ */
+ private class ModelStubAcceptingPersonAddedWithNearDuplicate extends ModelStub {
+
+ final ArrayList personsAdded = new ArrayList<>();
+ private final Person nearDuplicate;
+
+ ModelStubAcceptingPersonAddedWithNearDuplicate(Person nearDuplicate) {
+ requireNonNull(nearDuplicate);
+ this.nearDuplicate = nearDuplicate;
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ requireNonNull(person);
+ return personsAdded.stream().anyMatch(person::isSamePerson);
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ requireNonNull(person);
+ personsAdded.add(person);
+ }
+
+ @Override
+ public List findNearDuplicates(Person person) {
+ requireNonNull(person);
+ // Return the name of the near duplicate
+ return Collections.singletonList(nearDuplicate.getName().toString());
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Appointment appointment) {
+ return false;
+ }
+
+ @Override
+ public boolean appointmentsOverlap(Collection appointments) {
+ return false;
+ }
}
+
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..89a60775f2b 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -3,9 +3,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.testutil.Assert.assertThrows;
@@ -25,40 +29,84 @@
* Contains helper methods for testing commands.
*/
public class CommandTestUtil {
+ public static final String SPACE_PRECEDED_PREFIX_NAME = " " + PREFIX_NAME;
+ public static final String SPACE_PRECEDED_PREFIX_PHONE = " " + PREFIX_PHONE;
+ public static final String SPACE_PRECEDED_PREFIX_EMAIL = " " + PREFIX_EMAIL;
+ public static final String SPACE_PRECEDED_PREFIX_ADDRESS = " " + PREFIX_ADDRESS;
+ public static final String SPACE_PRECEDED_PREFIX_TAG = " " + PREFIX_TAG;
+ public static final String SPACE_PRECEDED_PREFIX_NOTE = " " + PREFIX_NOTE;
+ public static final String SPACE_PRECEDED_PREFIX_APPOINTMENT = " " + PREFIX_APPOINTMENT;
+ public static final String SPACE_PRECEDED_PREFIX_SUBJECT = " " + PREFIX_SUBJECT;
+ public static final String SPACE_PRECEDED_PREFIX_LEVEL = " " + PREFIX_LEVEL;
public static final String VALID_NAME_AMY = "Amy Bee";
public static final String VALID_NAME_BOB = "Bob Choo";
+ public static final String VALID_NAME_CELINE = "Celine Alfred";
public static final String VALID_PHONE_AMY = "11111111";
public static final String VALID_PHONE_BOB = "22222222";
+ public static final String VALID_PHONE_CELINE = "3333333";
public static final String VALID_EMAIL_AMY = "amy@example.com";
public static final String VALID_EMAIL_BOB = "bob@example.com";
+ public static final String VALID_EMAIL_CELINE = "celine@example.com";
public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
+ public static final String VALID_ADDRESS_CELINE = "Block 776, Cel Street 5";
public static final String VALID_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
+ public static final String VALID_NOTE_AMY = "She likes aardvarks.";
+ public static final String VALID_NOTE_BOB = "Favourite pastime: Eating";
+ public static final String VALID_NOTE_CELINE = "Sleepy";
+ public static final String VALID_APPOINTMENT_FRIDAY = "18:00-20:00 FRI";
+ public static final String VALID_APPOINTMENT_SUNDAY = "08:00-10:00 SUN";
+ public static final String VALID_SUBJECT_MATH = "Math";
+ public static final String VALID_SUBJECT_MT = "MT";
+ public static final String VALID_SUBJECT_BOB = "Math";
+ public static final String VALID_SUBJECT_CELINE = "MT";
+ public static final String VALID_LEVEL_P1 = "P1";
+ public static final String VALID_LEVEL_BOB = "P2";
+ public static final String VALID_LEVEL_CELINE = "P5";
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
+ public static final String NAME_DESC_CELINE = " " + PREFIX_NAME + VALID_NAME_CELINE;
public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY;
public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB;
+ public static final String PHONE_DESC_CELINE = " " + PREFIX_PHONE + VALID_PHONE_CELINE;
public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY;
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
+ public static final String EMAIL_DESC_CELINE = " " + PREFIX_EMAIL + VALID_EMAIL_CELINE;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
+ public static final String ADDRESS_DESC_CELINE = " " + PREFIX_ADDRESS + VALID_ADDRESS_CELINE;
public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
+ public static final String NOTE_DESC_AMY = " " + PREFIX_NOTE + VALID_NOTE_AMY;
+ public static final String NOTE_DESC_BOB = " " + PREFIX_NOTE + VALID_NOTE_BOB;
+ public static final String NOTE_DESC_CELINE = " " + PREFIX_NOTE + VALID_NOTE_CELINE;
+ public static final String APPOINTMENT_DESC_FRIDAY = " " + PREFIX_APPOINTMENT + VALID_APPOINTMENT_FRIDAY;
+ public static final String APPOINTMENT_DESC_SUNDAY = " " + PREFIX_APPOINTMENT + VALID_APPOINTMENT_SUNDAY;
+ public static final String SUBJECT_DESC_MATH = " " + PREFIX_SUBJECT + VALID_SUBJECT_MATH;
+ public static final String SUBJECT_DESC_MT = " " + PREFIX_SUBJECT + VALID_SUBJECT_MT;
+ public static final String SUBJECT_DESC_BOB = " " + PREFIX_SUBJECT + VALID_SUBJECT_BOB;
+ public static final String SUBJECT_DESC_CELINE = " " + PREFIX_SUBJECT + VALID_SUBJECT_CELINE;
+ public static final String LEVEL_DESC_P1 = " " + PREFIX_LEVEL + VALID_LEVEL_P1;
+ public static final String LEVEL_DESC_BOB = " " + PREFIX_LEVEL + VALID_LEVEL_BOB;
+ public static final String LEVEL_DESC_CELINE = " " + PREFIX_LEVEL + VALID_LEVEL_CELINE;
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
- public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
+ public static final String INVALID_APPOINTMENT_DESC = " " + PREFIX_APPOINTMENT + "aaaaaaaa"; // not in date format
+ public static final String INVALID_SUBJECT_DESC = " " + PREFIX_SUBJECT + "Gaming"; // not a valid subject
+ public static final String INVALID_LEVEL_DESC = " " + PREFIX_LEVEL + "P10"; // not a valid level
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
public static final EditCommand.EditPersonDescriptor DESC_AMY;
public static final EditCommand.EditPersonDescriptor DESC_BOB;
+ public static final EditCommand.EditPersonDescriptor DESC_CELINE;
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
@@ -67,6 +115,9 @@ public class CommandTestUtil {
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ DESC_CELINE = new EditPersonDescriptorBuilder().withName(VALID_NAME_CELINE)
+ .withPhone(VALID_PHONE_CELINE).withEmail(VALID_EMAIL_CELINE).withAddress(VALID_ADDRESS_CELINE)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY).build();
}
/**
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..7c440cfea32 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -75,7 +76,8 @@ public void execute_noFieldSpecifiedUnfilteredList_success() {
EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor());
Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
+ String expectedMessage = String.format(EditCommand.MESSAGE_NEAR_DUPLICATES, Messages.format(editedPerson),
+ String.join(", ", ""));
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
@@ -146,6 +148,26 @@ public void execute_invalidPersonIndexFilteredList_failure() {
assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_overlappingAppointmentUnfilteredList_failure() {
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withAppointments(
+ "10:00-13:00 SUN").build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_OVERLAPPING_APPOINTMENT);
+ }
+
+ @Test
+ public void execute_overlappingAppointments_failure() {
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withAppointments(
+ VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_FRIDAY).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_OVERLAPPING_APPOINTMENT);
+ }
+
@Test
public void equals() {
final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..76ad50628ad 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -5,7 +5,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
@@ -55,6 +58,21 @@ public void equals() {
// different tags -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build();
assertFalse(DESC_AMY.equals(editedAmy));
+
+ // different appointments -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAppointments(VALID_APPOINTMENT_FRIDAY).build();
+ assertFalse(DESC_AMY.equals(editedAmy));
+
+ // no appointment and appointments -> returns false
+ EditPersonDescriptor editedCeline = new EditPersonDescriptorBuilder(DESC_CELINE)
+ .withAppointments(VALID_APPOINTMENT_SUNDAY).build();
+ assertFalse(DESC_CELINE.equals(editedCeline));
+
+ // different values -> returns false
+ assertFalse(DESC_AMY.equals(DESC_CELINE));
+
+ // different values -> returns false
+ assertFalse(DESC_BOB.equals(DESC_CELINE));
}
@Test
@@ -64,8 +82,12 @@ public void toStringMethod() {
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
- + editPersonDescriptor.getTags().orElse(null) + "}";
+ + editPersonDescriptor.getAddress().orElse(null) + ", note="
+ + editPersonDescriptor.getNote().orElse(null) + ", appointments="
+ + editPersonDescriptor.getAppointments().orElse(null) + ", tags="
+ + editPersonDescriptor.getTags().orElse(null) + ", subjects="
+ + editPersonDescriptor.getSubjects().orElse(null) + ", level="
+ + editPersonDescriptor.getLevel().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ViewAppointmentsCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewAppointmentsCommandTest.java
new file mode 100644
index 00000000000..12c9c6740ea
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ViewAppointmentsCommandTest.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.time.DayOfWeek;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.appointment.AppointmentIsDayOfWeekPredicate;
+
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for ViewAppointmentsCommand.
+ */
+public class ViewAppointmentsCommandTest {
+
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ }
+
+ // Test modified to account for revised Appointment command where contact name is shown
+ @Test
+ public void execute_viewAppointments_success() {
+ String expectedMessage = "Appointments:\n"
+ + "Benson Meier: 12:00-13:00 SUN";
+ assertCommandSuccess(new ViewAppointmentsCommand(
+ new AppointmentIsDayOfWeekPredicate(List.of(DayOfWeek.SUNDAY))), model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_viewAppointmentsWithPredicate_success() {
+ String expectedMessage = "Appointments:\n"
+ + "There are no appointments to show!";
+ assertCommandSuccess(new ViewAppointmentsCommand(
+ new AppointmentIsDayOfWeekPredicate(List.of(DayOfWeek.MONDAY))), model, expectedMessage, expectedModel);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
new file mode 100644
index 00000000000..9c04289aa61
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
@@ -0,0 +1,149 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for {@code ViewCommand}.
+ *
+ * Code borrows heavily from DeleteCommandTest.java due to similarity in functionality between
+ * DeleteCommand and ViewCommand.
+ */
+public class ViewCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ List expected = new ArrayList<>();
+ expected.add("ALICE PAULINE\n");
+ expected.add("\n[P1] [friends]\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nDETAILS:\n");
+ expected.add("94351253\n");
+ expected.add("alice@example.com\n");
+ expected.add("123, Jurong West Ave 6, #08-111\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nAPPOINTMENTS:\n");
+ expected.add("-\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nNOTES:\nShe likes aardvarks.");
+
+ StringBuilder sb = new StringBuilder();
+ for (String str : expected) {
+ sb.append(str);
+ }
+ String expectedResult = sb.toString().trim();
+
+ assertCommandSuccess(new ViewCommand(INDEX_FIRST_PERSON), model, expectedResult, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ ViewCommand viewCommand = new ViewCommand(outOfBoundIndex);
+
+ assertCommandFailure(viewCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_validIndexFilteredList_success() {
+ showPersonAtIndex(model, INDEX_SECOND_PERSON);
+ showPersonAtIndex(expectedModel, INDEX_SECOND_PERSON);
+
+ List expected = new ArrayList<>();
+ expected.add("BENSON MEIER\n");
+ expected.add("\n[P1] [MATH] [owesMoney] [friends]\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nDETAILS:\n");
+ expected.add("98765432\n");
+ expected.add("johnd@example.com\n");
+ expected.add("311, Clementi Ave 2, #02-25\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nAPPOINTMENTS:\n");
+ expected.add("12:00-13:00 SUN\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nNOTES:\nHe can't take beer!");
+
+ StringBuilder sb = new StringBuilder();
+ for (String str : expected) {
+ sb.append(str);
+ }
+ String expectedResult = sb.toString().trim();
+
+ assertCommandSuccess(new ViewCommand(INDEX_FIRST_PERSON), model, expectedResult, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ ViewCommand viewCommand = new ViewCommand(outOfBoundIndex);
+
+ assertCommandFailure(viewCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ ViewCommand viewFirstCommand = new ViewCommand(INDEX_FIRST_PERSON);
+ ViewCommand viewSecondCommand = new ViewCommand(INDEX_SECOND_PERSON);
+
+ // same object -> returns true
+ assertTrue(viewFirstCommand.equals(viewFirstCommand));
+
+ // same values -> returns true
+ ViewCommand viewFirstCommandCopy = new ViewCommand(INDEX_FIRST_PERSON);
+ assertTrue(viewFirstCommand.equals(viewFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(viewFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(viewFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(viewFirstCommand.equals(viewSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ ViewCommand viewCommand = new ViewCommand(targetIndex);
+ String expected = ViewCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ assertEquals(expected, viewCommand.toString());
+ }
+
+ /**
+ * Updates {@code model}'s filtered list to show no one.
+ */
+ private void showNoPerson(Model model) {
+ model.updateFilteredPersonList(p -> false);
+
+ assertTrue(model.getFilteredPersonList().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..6af3494cfee 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -3,25 +3,55 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.APPOINTMENT_DESC_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.APPOINTMENT_DESC_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_LEVEL_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.LEVEL_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.LEVEL_DESC_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.NOTE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.NOTE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.NOTE_DESC_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_ADDRESS;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_APPOINTMENT;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_EMAIL;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_LEVEL;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_NOTE;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_PHONE;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_SUBJECT;
+import static seedu.address.logic.commands.CommandTestUtil.SPACE_PRECEDED_PREFIX_TAG;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_MATH;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_MT;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_MATH;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_MT;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
@@ -32,13 +62,19 @@
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalPersons.AMY;
import static seedu.address.testutil.TypicalPersons.BOB;
+import static seedu.address.testutil.TypicalPersons.CELINE;
+import static seedu.address.testutil.TypicalPersons.NAME_ONLY_CELINE;
+import static seedu.address.testutil.TypicalPersons.NO_ADDRESS_AMY;
+import static seedu.address.testutil.TypicalPersons.NO_EMAIL_AMY;
+import static seedu.address.testutil.TypicalPersons.NO_NOTE_AMY;
+import static seedu.address.testutil.TypicalPersons.NO_PHONE_AMY;
import org.junit.jupiter.api.Test;
import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
-import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -54,20 +90,70 @@ public void parse_allFieldsPresent_success() {
// whitespace only preamble
assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ + ADDRESS_DESC_BOB + NOTE_DESC_BOB + LEVEL_DESC_BOB
+ + TAG_DESC_FRIEND + SUBJECT_DESC_BOB, new AddCommand(expectedPerson));
// multiple tags - all accepted
Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND
+ + LEVEL_DESC_BOB + SUBJECT_DESC_MATH,
new AddCommand(expectedPersonMultipleTags));
+
+ // multiple appointments - all accepted
+ Person expectedPersonMultipleAppointments = new PersonBuilder(CELINE)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_SUNDAY)
+ .withSubjects(VALID_SUBJECT_CELINE)
+ .build();
+ assertParseSuccess(parser,
+ NAME_DESC_CELINE + PHONE_DESC_CELINE + EMAIL_DESC_CELINE + ADDRESS_DESC_CELINE
+ + NOTE_DESC_CELINE + APPOINTMENT_DESC_FRIDAY + APPOINTMENT_DESC_SUNDAY
+ + LEVEL_DESC_CELINE + SUBJECT_DESC_CELINE,
+ new AddCommand(expectedPersonMultipleAppointments));
+
+ // multiple subjects - all accepted
+ Person expectedPersonMultipleSubjects = new PersonBuilder(BOB)
+ .withTags(VALID_TAG_FRIEND)
+ .withSubjects(VALID_SUBJECT_BOB, VALID_SUBJECT_MT)
+ .build();
+ assertParseSuccess(parser,
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + NOTE_DESC_BOB + TAG_DESC_FRIEND
+ + LEVEL_DESC_BOB + SUBJECT_DESC_BOB + SUBJECT_DESC_MT,
+ new AddCommand(expectedPersonMultipleSubjects));
+
+ // multiple tags AND appointments - all accepted
+ Person expectedPersonMultipleAppointmentsAndTags = new PersonBuilder(BOB)
+ .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_SUNDAY)
+ .build();
+ assertParseSuccess(parser,
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND
+ + APPOINTMENT_DESC_FRIDAY + APPOINTMENT_DESC_SUNDAY
+ + SUBJECT_DESC_BOB + LEVEL_DESC_BOB,
+ new AddCommand(expectedPersonMultipleAppointmentsAndTags));
+
+ // multiple tags, appointments AND subjects - all accepted
+ Person expectedPersonMultipleTagsAppointmentsAndSubjects = new PersonBuilder(CELINE)
+ .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_SUNDAY)
+ .withSubjects(VALID_SUBJECT_CELINE, VALID_SUBJECT_MATH)
+ .build();
+ assertParseSuccess(parser,
+ NAME_DESC_CELINE + PHONE_DESC_CELINE + EMAIL_DESC_CELINE + ADDRESS_DESC_CELINE
+ + NOTE_DESC_CELINE + TAG_DESC_HUSBAND + TAG_DESC_FRIEND
+ + APPOINTMENT_DESC_FRIDAY + APPOINTMENT_DESC_SUNDAY
+ + SUBJECT_DESC_CELINE + SUBJECT_DESC_MATH + LEVEL_DESC_CELINE,
+ new AddCommand(expectedPersonMultipleTagsAppointmentsAndSubjects));
}
@Test
public void parse_repeatedNonTagValue_failure() {
- String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + NOTE_DESC_BOB
+ ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
// multiple names
@@ -106,10 +192,6 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
-
// valid value followed by invalid value
// invalid name
@@ -123,18 +205,89 @@ public void parse_repeatedNonTagValue_failure() {
// invalid phone
assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
-
- // invalid address
- assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
}
@Test
public void parse_optionalFieldsMissing_success() {
- // zero tags
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
+ // zero tags or subjects
+ Person expectedPerson = new PersonBuilder(AMY).withTags().removeLevel().build();
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY,
+ new AddCommand(expectedPerson));
+ // empty subject prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_SUBJECT,
new AddCommand(expectedPerson));
+ // empty tag prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_TAG,
+ new AddCommand(expectedPerson));
+ // empty appointment prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_APPOINTMENT,
+ new AddCommand(expectedPerson));
+
+ //No email field
+ Person expectedPersonNoEmail = new PersonBuilder(NO_EMAIL_AMY).withTags().removeLevel().build();
+ //No prefix
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY,
+ new AddCommand(expectedPersonNoEmail));
+ //Empty prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_EMAIL,
+ new AddCommand(expectedPersonNoEmail));
+
+ //No address field
+ Person expectedPersonNoAddress = new PersonBuilder(NO_ADDRESS_AMY).withTags().removeLevel().build();
+ //No prefix
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + NOTE_DESC_AMY,
+ new AddCommand(expectedPersonNoAddress));
+ //Empty prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_ADDRESS,
+ new AddCommand(expectedPersonNoAddress));
+
+ //No phone field
+ Person expectedPersonNoPhone = new PersonBuilder(NO_PHONE_AMY).withTags().removeLevel().build();
+ //No prefix
+ assertParseSuccess(parser, NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY,
+ new AddCommand(expectedPersonNoPhone));
+ //Empty prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NOTE_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_PHONE,
+ new AddCommand(expectedPersonNoPhone));
+
+ //No Note field
+ Person expectedPersonNoNote = new PersonBuilder(NO_NOTE_AMY).withTags().removeLevel().build();
+ //No prefix
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
+ new AddCommand(expectedPersonNoNote));
+ //Empty prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_NOTE,
+ new AddCommand(expectedPersonNoNote));
+ //Multiple empty prefix values
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + SPACE_PRECEDED_PREFIX_NOTE + SPACE_PRECEDED_PREFIX_NOTE,
+ new AddCommand(expectedPersonNoNote));
+
+ //No Level field
+ Person expectedPersonNoLevel = new PersonBuilder(AMY).withTags().removeLevel().build();
+ //No prefix
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + NOTE_DESC_AMY,
+ new AddCommand(expectedPersonNoLevel));
+ //Empty prefix value
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + NOTE_DESC_AMY + SPACE_PRECEDED_PREFIX_LEVEL,
+ new AddCommand(expectedPersonNoLevel));
+ //Multiple empty prefix values
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + NOTE_DESC_AMY + SPACE_PRECEDED_PREFIX_LEVEL + SPACE_PRECEDED_PREFIX_LEVEL,
+ new AddCommand(expectedPersonNoLevel));
+
+ //Only Name field
+ Person expectedPersonNameOnly = new PersonBuilder(NAME_ONLY_CELINE).withTags().removeLevel().build();
+ assertParseSuccess(parser, NAME_DESC_CELINE, new AddCommand(expectedPersonNameOnly));
}
@Test
@@ -142,55 +295,43 @@ public void parse_compulsoryFieldMissing_failure() {
String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE);
// missing name prefix
- assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
+ assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + NOTE_DESC_BOB,
expectedMessage);
// all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB
+ + VALID_ADDRESS_BOB + VALID_NOTE_BOB, expectedMessage);
}
@Test
public void parse_invalidValue_failure() {
// invalid name
assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
// invalid phone
assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
// invalid email
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
-
- // invalid address
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
// invalid tag
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+ + NOTE_DESC_BOB + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+
+ // invalid level
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + INVALID_LEVEL_DESC, Level.MESSAGE_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + NOTE_DESC_BOB, Name.MESSAGE_CONSTRAINTS);
// non-empty preamble
assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ + ADDRESS_DESC_BOB + NOTE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..1bc0c33b1bf 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -22,6 +22,7 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ViewAppointmentsCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
@@ -88,6 +89,12 @@ public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_viewAppointments() throws Exception {
+ assertTrue(parser.parseCommand(ViewAppointmentsCommand.COMMAND_WORD) instanceof ViewAppointmentsCommand);
+ }
+
+
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
diff --git a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
index c97308935f5..2b636e64a79 100644
--- a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
+++ b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.parser.CliSyntax.INCORRECT_PREFIX_MAP;
import org.junit.jupiter.api.Test;
@@ -17,7 +18,7 @@ public class ArgumentTokenizerTest {
@Test
public void tokenize_emptyArgsString_noValues() {
String argsString = " ";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash);
assertPreambleEmpty(argMultimap);
assertArgumentAbsent(argMultimap, pSlash);
@@ -56,7 +57,7 @@ private void assertArgumentAbsent(ArgumentMultimap argMultimap, Prefix prefix) {
@Test
public void tokenize_noPrefixes_allTakenAsPreamble() {
String argsString = " some random string /t tag with leading and trailing spaces ";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP);
// Same string expected as preamble, but leading/trailing spaces should be trimmed
assertPreamblePresent(argMultimap, argsString.trim());
@@ -67,13 +68,13 @@ public void tokenize_noPrefixes_allTakenAsPreamble() {
public void tokenize_oneArgument() {
// Preamble present
String argsString = " Some preamble string p/ Argument value ";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash);
assertPreamblePresent(argMultimap, "Some preamble string");
assertArgumentPresent(argMultimap, pSlash, "Argument value");
// No preamble
argsString = " p/ Argument value ";
- argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash);
+ argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash);
assertPreambleEmpty(argMultimap);
assertArgumentPresent(argMultimap, pSlash, "Argument value");
@@ -83,7 +84,8 @@ public void tokenize_oneArgument() {
public void tokenize_multipleArguments() {
// Only two arguments are present
String argsString = "SomePreambleString -t dashT-Value p/pSlash value";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP,
+ pSlash, dashT, hatQ);
assertPreamblePresent(argMultimap, "SomePreambleString");
assertArgumentPresent(argMultimap, pSlash, "pSlash value");
assertArgumentPresent(argMultimap, dashT, "dashT-Value");
@@ -91,7 +93,7 @@ public void tokenize_multipleArguments() {
// All three arguments are present
argsString = "Different Preamble String ^Q111 -t dashT-Value p/pSlash value";
- argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash, dashT, hatQ);
assertPreamblePresent(argMultimap, "Different Preamble String");
assertArgumentPresent(argMultimap, pSlash, "pSlash value");
assertArgumentPresent(argMultimap, dashT, "dashT-Value");
@@ -102,7 +104,7 @@ public void tokenize_multipleArguments() {
// Reuse tokenizer on an empty string to ensure ArgumentMultimap is correctly reset
// (i.e. no stale values from the previous tokenizing remain)
argsString = "";
- argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash, dashT, hatQ);
assertPreambleEmpty(argMultimap);
assertArgumentAbsent(argMultimap, pSlash);
@@ -110,7 +112,7 @@ public void tokenize_multipleArguments() {
// Prefixes not previously given to the tokenizer should not return any values
argsString = unknownPrefix + "some value";
- argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP, pSlash, dashT, hatQ);
assertArgumentAbsent(argMultimap, unknownPrefix);
assertPreamblePresent(argMultimap, argsString); // Unknown prefix is taken as part of preamble
}
@@ -119,7 +121,8 @@ public void tokenize_multipleArguments() {
public void tokenize_multipleArgumentsWithRepeats() {
// Two arguments repeated, some have empty values
String argsString = "SomePreambleString -t dashT-Value ^Q ^Q -t another dashT value p/ pSlash value -t";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP,
+ pSlash, dashT, hatQ);
assertPreamblePresent(argMultimap, "SomePreambleString");
assertArgumentPresent(argMultimap, pSlash, "pSlash value");
assertArgumentPresent(argMultimap, dashT, "dashT-Value", "another dashT value", "");
@@ -129,7 +132,8 @@ public void tokenize_multipleArgumentsWithRepeats() {
@Test
public void tokenize_multipleArgumentsJoined() {
String argsString = "SomePreambleStringp/ pSlash joined-tjoined -t not joined^Qjoined";
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, INCORRECT_PREFIX_MAP,
+ pSlash, dashT, hatQ);
assertPreamblePresent(argMultimap, "SomePreambleStringp/ pSlash joined-tjoined");
assertArgumentAbsent(argMultimap, pSlash);
assertArgumentPresent(argMultimap, dashT, "not joined^Qjoined");
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..ab2e741ba7a 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -3,9 +3,11 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.APPOINTMENT_DESC_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.APPOINTMENT_DESC_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_APPOINTMENT_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
@@ -13,18 +15,26 @@
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_MATH;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_MT;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_MATH;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_MT;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
@@ -38,7 +48,7 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
+import seedu.address.model.appointment.Appointment;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
@@ -48,6 +58,8 @@
public class EditCommandParserTest {
private static final String TAG_EMPTY = " " + PREFIX_TAG;
+ private static final String APPOINTMENT_EMPTY = " " + PREFIX_APPOINTMENT;
+ private static final String SUBJECT_EMPTY = " " + PREFIX_SUBJECT;
private static final String MESSAGE_INVALID_FORMAT =
String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE);
@@ -86,23 +98,61 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email
- assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address
assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
+ assertParseFailure(parser, "1" + INVALID_APPOINTMENT_DESC, Appointment.MESSAGE_CONSTRAINTS);
+ //invalid appointment
// invalid phone followed by valid email
assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
- // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
- // parsing it together with a valid tag results in error
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
-
// multiple invalid values, but only the first invalid value is captured
assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
Name.MESSAGE_CONSTRAINTS);
}
+ @Test
+ public void parse_multipleEmptyAndFilledValues_success() {
+ // Parser should allow any combination of empty fields and filled fields for fields containing multiple values
+ Index targetIndex = INDEX_FIRST_PERSON;
+
+ // Tags
+ EditPersonDescriptor personWithTags = new EditPersonDescriptorBuilder()
+ .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build();
+ EditCommand expectedCommandTags = new EditCommand(targetIndex, personWithTags);
+ assertParseSuccess(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, expectedCommandTags);
+ assertParseSuccess(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, expectedCommandTags);
+ assertParseSuccess(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, expectedCommandTags);
+ assertParseSuccess(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND
+ + TAG_EMPTY, expectedCommandTags);
+
+ // Appointments
+ EditPersonDescriptor personWithAppointments = new EditPersonDescriptorBuilder()
+ .withAppointments(VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_SUNDAY).build();
+ EditCommand expectedCommandAppointments = new EditCommand(targetIndex, personWithAppointments);
+ assertParseSuccess(parser, "1" + APPOINTMENT_DESC_FRIDAY
+ + APPOINTMENT_DESC_SUNDAY + APPOINTMENT_EMPTY, expectedCommandAppointments);
+ assertParseSuccess(parser, "1" + APPOINTMENT_DESC_FRIDAY
+ + APPOINTMENT_EMPTY + APPOINTMENT_DESC_SUNDAY, expectedCommandAppointments);
+ assertParseSuccess(parser, "1" + APPOINTMENT_EMPTY
+ + APPOINTMENT_DESC_FRIDAY + APPOINTMENT_DESC_SUNDAY, expectedCommandAppointments);
+ assertParseSuccess(parser, "1" + APPOINTMENT_EMPTY
+ + APPOINTMENT_DESC_FRIDAY + APPOINTMENT_EMPTY + APPOINTMENT_DESC_SUNDAY
+ + APPOINTMENT_EMPTY, expectedCommandAppointments);
+
+ // Subjects
+ EditPersonDescriptor personWithSubjects = new EditPersonDescriptorBuilder()
+ .withSubjects(VALID_SUBJECT_MT, VALID_SUBJECT_MATH).build();
+ EditCommand expectedCommandSubjects = new EditCommand(targetIndex, personWithSubjects);
+ assertParseSuccess(parser, "1" + SUBJECT_DESC_MATH
+ + SUBJECT_DESC_MT + SUBJECT_EMPTY, expectedCommandSubjects);
+ assertParseSuccess(parser, "1" + SUBJECT_DESC_MATH
+ + SUBJECT_EMPTY + SUBJECT_DESC_MT, expectedCommandSubjects);
+ assertParseSuccess(parser, "1" + SUBJECT_EMPTY
+ + SUBJECT_DESC_MATH + SUBJECT_DESC_MT, expectedCommandSubjects);
+ assertParseSuccess(parser, "1" + SUBJECT_EMPTY + SUBJECT_DESC_MATH
+ + SUBJECT_EMPTY + SUBJECT_DESC_MT + SUBJECT_EMPTY, expectedCommandSubjects);
+ }
+
@Test
public void parse_allFieldsSpecified_success() {
Index targetIndex = INDEX_SECOND_PERSON;
@@ -188,8 +238,8 @@ public void parse_multipleRepeatedFields_failure() {
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
// multiple invalid values
- userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC
- + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC;
+ userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + ADDRESS_DESC_BOB + INVALID_EMAIL_DESC
+ + INVALID_PHONE_DESC + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB;
assertParseFailure(parser, userInput,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..6f739c6efbc 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -14,8 +14,15 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmptyAddress;
+import seedu.address.model.person.EmptyEmail;
+import seedu.address.model.person.EmptyLevel;
+import seedu.address.model.person.EmptyNote;
+import seedu.address.model.person.EmptyPhone;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -26,6 +33,7 @@ public class ParserUtilTest {
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_APPOINTMENT = "jjjjjjjj";
private static final String VALID_NAME = "Rachel Walker";
private static final String VALID_PHONE = "123456";
@@ -33,6 +41,8 @@ public class ParserUtilTest {
private static final String VALID_EMAIL = "rachel@example.com";
private static final String VALID_TAG_1 = "friend";
private static final String VALID_TAG_2 = "neighbour";
+ private static final String VALID_APPOINTMENT_1 = "12:00-13:00 WED";
+ private static final String VALID_APPOINTMENT_2 = "00:00-01:00 SUN";
private static final String WHITESPACE = " \t\r\n";
@@ -79,11 +89,6 @@ public void parseName_validValueWithWhitespace_returnsTrimmedName() throws Excep
assertEquals(expectedName, ParserUtil.parseName(nameWithWhitespace));
}
- @Test
- public void parsePhone_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parsePhone((String) null));
- }
-
@Test
public void parsePhone_invalidValue_throwsParseException() {
assertThrows(ParseException.class, () -> ParserUtil.parsePhone(INVALID_PHONE));
@@ -102,11 +107,6 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc
assertEquals(expectedPhone, ParserUtil.parsePhone(phoneWithWhitespace));
}
- @Test
- public void parseAddress_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
- }
-
@Test
public void parseAddress_invalidValue_throwsParseException() {
assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS));
@@ -125,11 +125,6 @@ public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws
assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace));
}
- @Test
- public void parseEmail_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null));
- }
-
@Test
public void parseEmail_invalidValue_throwsParseException() {
assertThrows(ParseException.class, () -> ParserUtil.parseEmail(INVALID_EMAIL));
@@ -193,4 +188,82 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
assertEquals(expectedTagSet, actualTagSet);
}
+ @Test
+ public void parseAppointment_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseAppointments(null));
+ }
+
+ @Test
+ public void parseAppointment_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseAppointment(INVALID_APPOINTMENT));
+ }
+
+ @Test
+ public void parseAppointment_validValueWithoutWhitespace_returnsAppointment() throws Exception {
+ Appointment expectedAppointment = new Appointment(VALID_APPOINTMENT_1);
+ assertEquals(expectedAppointment, ParserUtil.parseAppointment(VALID_APPOINTMENT_1));
+ }
+
+ @Test
+ public void parseAppointment_validValueWithWhitespace_returnsTrimmedAppointment() throws Exception {
+ String appointmentWithWhitespace = WHITESPACE + VALID_APPOINTMENT_1 + WHITESPACE;
+ Appointment expectedAppointment = new Appointment(VALID_APPOINTMENT_1);
+ assertEquals(expectedAppointment, ParserUtil.parseAppointment(appointmentWithWhitespace));
+ }
+
+ @Test
+ public void parseAppointments_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseAppointments(null));
+ }
+
+ @Test
+ public void parseAppointments_collectionWithInvalidAppointments_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil
+ .parseAppointments(Arrays.asList(VALID_APPOINTMENT_1, INVALID_APPOINTMENT)));
+ }
+
+ @Test
+ public void parseAppointments_emptyCollection_returnsEmptySet() throws Exception {
+ assertTrue(ParserUtil.parseAppointments(Collections.emptyList()).isEmpty());
+ }
+
+ @Test
+ public void parseAppointments_collectionWithValidAppointments_returnsAppointmentSet() throws Exception {
+ AppointmentList actualAppointmentSet = ParserUtil
+ .parseAppointments(Arrays.asList(VALID_APPOINTMENT_1, VALID_APPOINTMENT_2));
+ AppointmentList expectedAppointmentSet = new AppointmentList();
+ expectedAppointmentSet.addAll(Arrays
+ .asList(new Appointment(VALID_APPOINTMENT_1), new Appointment(VALID_APPOINTMENT_2)));
+
+ assertEquals(expectedAppointmentSet, actualAppointmentSet);
+ }
+
+ @Test
+ public void parseEmptyFields_returnsEmptyFieldType() throws Exception {
+ //Creating empty Phones
+ assertEquals(ParserUtil.parsePhone(""), new EmptyPhone());
+ assertEquals(ParserUtil.parsePhone(null), new EmptyPhone());
+
+ //Creating empty Addresses
+ assertEquals(ParserUtil.parseAddress(""), new EmptyAddress());
+ assertEquals(ParserUtil.parseAddress(null), new EmptyAddress());
+
+ //Creating empty Emails
+ assertEquals(ParserUtil.parseEmail(""), new EmptyEmail());
+ assertEquals(ParserUtil.parseEmail(null), new EmptyEmail());
+
+ //Creating empty Notes
+ assertEquals(ParserUtil.parseNote(""), new EmptyNote());
+ assertEquals(ParserUtil.parseNote(null), new EmptyNote());
+
+ //Creating empty Subjects
+ assertEquals(ParserUtil.parseSubjects(Collections.emptyList()), new HashSet<>());
+ assertEquals(ParserUtil.parseSubjects(Arrays.asList("")), new HashSet<>());
+ assertEquals(ParserUtil.parseSubjects(Arrays.asList(" ")), new HashSet<>());
+ assertEquals(ParserUtil.parseSubjects(Arrays.asList("", " ")), new HashSet<>());
+
+ //Creating empty Levels
+ assertEquals(ParserUtil.parseLevel(""), new EmptyLevel());
+ assertEquals(ParserUtil.parseLevel(null), new EmptyLevel());
+ }
}
diff --git a/src/test/java/seedu/address/logic/parser/ViewAppointmentsCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ViewAppointmentsCommandParserTest.java
new file mode 100644
index 00000000000..bbac4ca16e0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ViewAppointmentsCommandParserTest.java
@@ -0,0 +1,52 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.time.DayOfWeek;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ViewAppointmentsCommand;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentIsDayOfWeekPredicate;
+
+public class ViewAppointmentsCommandParserTest {
+
+ private ViewAppointmentsCommandParser parser = new ViewAppointmentsCommandParser();
+
+ @Test
+ public void parse_emptyArg_returnsViewAppointmentsCommand() {
+ ViewAppointmentsCommand expectedViewAppointmentsCommand =
+ new ViewAppointmentsCommand(new AppointmentIsDayOfWeekPredicate(
+ new ArrayList<>(Appointment.DAY_OF_WEEK_TO_NUM.keySet())));
+ // whitespace
+ assertParseSuccess(parser, " ", expectedViewAppointmentsCommand);
+
+ // weird whitespace
+ assertParseSuccess(parser, " \n \t", expectedViewAppointmentsCommand);
+ }
+
+ @Test
+ public void parse_validArgs_returnsViewAppointmentsCommand() {
+ // no leading and trailing whitespaces
+ ViewAppointmentsCommand expectedViewAppointmentsCommand =
+ new ViewAppointmentsCommand(new AppointmentIsDayOfWeekPredicate(
+ Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.TUESDAY)));
+ assertParseSuccess(parser, "mon Tue", expectedViewAppointmentsCommand);
+
+ // multiple whitespaces between days
+ assertParseSuccess(parser, " \n mon \n \t Tue \t", expectedViewAppointmentsCommand);
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ // not days
+ assertParseFailure(parser, "mon Tues",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewAppointmentsCommand.MESSAGE_USAGE));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java
new file mode 100644
index 00000000000..2f84b9a4055
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ViewCommand;
+
+/**
+ * Code borrows heavily from DeleteCommandParserTest.java due to similarity
+ * in functionality between DeleteCommand and ViewCommand.
+ */
+public class ViewCommandParserTest {
+ private ViewCommandParser parser = new ViewCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsDeleteCommand() {
+ assertParseSuccess(parser, "1", new ViewCommand(INDEX_FIRST_PERSON));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..df73841d476 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -4,9 +4,11 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import java.util.Arrays;
@@ -18,8 +20,10 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.appointment.Appointment;
import seedu.address.model.person.Person;
import seedu.address.model.person.exceptions.DuplicatePersonException;
+import seedu.address.model.person.exceptions.PersonNotFoundException;
import seedu.address.testutil.PersonBuilder;
public class AddressBookTest {
@@ -47,9 +51,11 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
public void resetData_withDuplicatePersons_throwsDuplicatePersonException() {
// Two persons with the same identity fields
Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY)
.build();
List newPersons = Arrays.asList(ALICE, editedAlice);
- AddressBookStub newData = new AddressBookStub(newPersons);
+ List newAppointments = editedAlice.getAppointments().asUnmodifiableObservableList();
+ AddressBookStub newData = new AddressBookStub(newPersons, newAppointments);
assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData));
}
@@ -78,11 +84,56 @@ public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() {
assertTrue(addressBook.hasPerson(editedAlice));
}
+ @Test
+ public void addPerson_personInAddressBook_throwDuplicatePersonException() {
+ addressBook.addPerson(ALICE);
+ assertThrows(DuplicatePersonException.class, () -> addressBook.addPerson(ALICE));
+ }
+
+ @Test
+ public void addPerson_personNotInAddressBook_success() {
+ assertFalse(addressBook.hasPerson(BENSON));
+ assertTrue(addressBook.getAppointmentList().isEmpty());
+ addressBook.addPerson(BENSON);
+ assertTrue(addressBook.hasPerson(BENSON));
+ assertFalse(BENSON.getAppointments().isEmpty());
+ assertFalse(addressBook.getAppointmentList().isEmpty());
+ }
+
+ @Test
+ public void removePerson_personInAddressBook_success() {
+ addressBook.addPerson(BENSON);
+ assertFalse(addressBook.getAppointmentList().isEmpty());
+
+ addressBook.removePerson(BENSON);
+ assertFalse(addressBook.hasPerson(BENSON));
+ assertTrue(addressBook.getAppointmentList().isEmpty());
+ }
+
+ @Test
+ public void removePerson_personNotInAddressBook_throwPersonNotFoundException() {
+ addressBook.addPerson(BENSON);
+ assertTrue(addressBook.hasPerson(BENSON));
+ assertThrows(PersonNotFoundException.class, () -> addressBook.removePerson(ALICE));
+ }
+
+
@Test
public void getPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0));
}
+ @Test
+ public void appointmentOverlaps_appointmentsInAddressBookDoNotOverlap_returnsFalse() {
+ assertFalse(addressBook.appointmentsOverlap(new Appointment(VALID_APPOINTMENT_FRIDAY)));
+ }
+
+ @Test
+ public void hasAppointment_appointmentInAddressBookOverlap_returnsTrue() {
+ addressBook.addAppointment(new Appointment(VALID_APPOINTMENT_FRIDAY));
+ assertTrue(addressBook.appointmentsOverlap(new Appointment(VALID_APPOINTMENT_FRIDAY)));
+ }
+
@Test
public void toStringMethod() {
String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList() + "}";
@@ -94,15 +145,22 @@ public void toStringMethod() {
*/
private static class AddressBookStub implements ReadOnlyAddressBook {
private final ObservableList persons = FXCollections.observableArrayList();
+ private final ObservableList appointments = FXCollections.observableArrayList();
- AddressBookStub(Collection persons) {
+ AddressBookStub(Collection persons, Collection appointments) {
this.persons.setAll(persons);
+ this.appointments.setAll(appointments);
}
@Override
public ObservableList getPersonList() {
return persons;
}
+
+ @Override
+ public ObservableList getAppointmentList() {
+ return appointments;
+ }
}
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..7725a8c642f 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -5,6 +5,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalAppointments.SUN_APPOINTMENT_10_TO_12;
+import static seedu.address.testutil.TypicalAppointments.SUN_APPOINTMENT_11_TO_13;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
@@ -15,6 +17,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.appointment.Appointment;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.testutil.AddressBookBuilder;
@@ -72,6 +75,7 @@ public void setAddressBookFilePath_validPath_setsAddressBookFilePath() {
assertEquals(path, modelManager.getAddressBookFilePath());
}
+ //// tests for person
@Test
public void hasPerson_nullPerson_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> modelManager.hasPerson(null));
@@ -93,6 +97,29 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
}
+ //// tests for appointment
+ @Test
+ public void appointmentOverlaps_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.appointmentsOverlap((Appointment) null));
+ }
+
+ @Test
+ public void appointmentOverlaps_appointmentsInAddressBookDoNotOverlap_returnsFalse() {
+ assertFalse(modelManager.appointmentsOverlap(SUN_APPOINTMENT_10_TO_12));
+ }
+
+ @Test
+ public void appointmentOverlaps_appointmentsInAddressBookOverlap_returnsTrue() {
+ modelManager.addPerson(BENSON);
+ assertTrue(modelManager.appointmentsOverlap(SUN_APPOINTMENT_11_TO_13));
+ }
+
+ @Test
+ public void getFilteredAppointmentList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredAppointmentList().remove(0));
+ }
+
+ // tests for util
@Test
public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
diff --git a/src/test/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicateTest.java b/src/test/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicateTest.java
new file mode 100644
index 00000000000..73b71ce1fdb
--- /dev/null
+++ b/src/test/java/seedu/address/model/appointment/AppointmentIsDayOfWeekPredicateTest.java
@@ -0,0 +1,80 @@
+package seedu.address.model.appointment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.DayOfWeek;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+public class AppointmentIsDayOfWeekPredicateTest {
+
+ @Test
+ public void equals() {
+ List firstPredicateDayOfWeekList = Collections.singletonList(DayOfWeek.MONDAY);
+ List secondPredicateDayOfWeekList = Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.TUESDAY);
+
+ AppointmentIsDayOfWeekPredicate firstPredicate =
+ new AppointmentIsDayOfWeekPredicate(firstPredicateDayOfWeekList);
+ AppointmentIsDayOfWeekPredicate secondPredicate =
+ new AppointmentIsDayOfWeekPredicate(secondPredicateDayOfWeekList);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ AppointmentIsDayOfWeekPredicate firstPredicateCopy =
+ new AppointmentIsDayOfWeekPredicate(firstPredicateDayOfWeekList);
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ // different types -> returns false
+ assertFalse(firstPredicate.equals(1));
+
+ // null -> returns false
+ assertFalse(firstPredicate.equals(null));
+
+ // different appointment -> returns false
+ assertFalse(firstPredicate.equals(secondPredicate));
+ }
+
+ @Test
+ public void test_dayOfWeekMatches_returnsTrue() {
+ // Single day match
+ AppointmentIsDayOfWeekPredicate predicate =
+ new AppointmentIsDayOfWeekPredicate(Collections.singletonList(DayOfWeek.MONDAY));
+ assertTrue(predicate.test(new Appointment("09:00-10:00 MON")));
+
+ // Multiple days match
+ predicate = new AppointmentIsDayOfWeekPredicate(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.TUESDAY));
+ assertTrue(predicate.test(new Appointment("09:00-10:00 TUE")));
+
+ // Mixed-case keywords
+ predicate = new AppointmentIsDayOfWeekPredicate(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.TUESDAY));
+ assertTrue(predicate.test(new Appointment("09:00-10:00 mon")));
+ }
+
+ @Test
+ public void test_dayOfWeekDoesNotMatch_returnsFalse() {
+ // No day match
+ AppointmentIsDayOfWeekPredicate predicate =
+ new AppointmentIsDayOfWeekPredicate(Collections.singletonList(DayOfWeek.MONDAY));
+ assertFalse(predicate.test(new Appointment("09:00-10:00 TUE")));
+
+ // Different day match
+ predicate = new AppointmentIsDayOfWeekPredicate(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY));
+ assertFalse(predicate.test(new Appointment("09:00-10:00 TUE")));
+ }
+
+ @Test
+ public void toStringMethod() {
+ List daysOfWeek = List.of(DayOfWeek.MONDAY, DayOfWeek.TUESDAY);
+ AppointmentIsDayOfWeekPredicate predicate = new AppointmentIsDayOfWeekPredicate(daysOfWeek);
+
+ String expected = AppointmentIsDayOfWeekPredicate.class.getCanonicalName() + "{days=" + daysOfWeek + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/appointment/AppointmentListTest.java b/src/test/java/seedu/address/model/appointment/AppointmentListTest.java
new file mode 100644
index 00000000000..0c72f7dc26e
--- /dev/null
+++ b/src/test/java/seedu/address/model/appointment/AppointmentListTest.java
@@ -0,0 +1,124 @@
+package seedu.address.model.appointment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.appointment.exceptions.AppointmentNotFoundException;
+
+public class AppointmentListTest {
+
+ private final AppointmentList appointmentList = new AppointmentList();
+ private final Appointment fridayAppointment = new Appointment("10:00-12:00 FRI");
+ private final Appointment sundayAppointment = new Appointment("10:00-12:00 SUN");
+ private final Appointment sundayOverlappingAppointment = new Appointment("11:00-12:00 SUN");
+
+ @Test
+ public void contains_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.contains(null));
+ }
+
+ @Test
+ public void contains_appointmentNotInList_returnsFalse() {
+ assertFalse(appointmentList.contains(fridayAppointment));
+ }
+
+ @Test
+ public void contains_appointmentInList_returnsTrue() {
+ appointmentList.add(fridayAppointment);
+ assertTrue(appointmentList.contains(fridayAppointment));
+ }
+
+ @Test
+ public void contains_overlappingAppointment_returnsTrue() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.add(sundayOverlappingAppointment);
+ assertTrue(appointmentList.isOverlapping());
+ }
+
+ @Test
+ public void add_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.add(null));
+ }
+
+ @Test
+ public void setAppointment_nullTargetAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointment(
+ null, sundayAppointment));
+ }
+
+ @Test
+ public void setAppointment_nullEditedAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointment(
+ sundayAppointment, null));
+ }
+
+ @Test
+ public void setAppointment_editedAppointmentIsDifferent_success() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.setAppointment(sundayAppointment, fridayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointment_targetAppointmentNotInList_throwsAppointmentNotFoundException() {
+ assertThrows(AppointmentNotFoundException.class, () -> appointmentList.setAppointment(
+ sundayOverlappingAppointment, sundayOverlappingAppointment));
+ }
+
+ @Test
+ public void remove_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.remove(null));
+ }
+
+ @Test
+ public void remove_appointmentDoesNotExist_throwsAppointmentNotFoundException() {
+ assertThrows(AppointmentNotFoundException.class, () -> appointmentList.remove(sundayAppointment));
+ }
+
+ @Test
+ public void remove_existingAppointment_removesAppointment() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.remove(sundayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointments_nullAppointmentList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointments((AppointmentList) null));
+ }
+
+ @Test
+ public void setAppointments_appointmentList_replacesOwnListWithProvidedAppointmentList() {
+ appointmentList.add(fridayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ appointmentList.setAppointments(expectedAppointmentList);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointments_nullList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointments((List) null));
+ }
+
+ @Test
+ public void setAppointments_list_replacesOwnListWithProvidedList() {
+ appointmentList.add(sundayAppointment);
+ List appointments = Collections.singletonList(fridayAppointment);
+ appointmentList.setAppointments(appointments);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/appointment/AppointmentTest.java b/src/test/java/seedu/address/model/appointment/AppointmentTest.java
new file mode 100644
index 00000000000..981febe9fc2
--- /dev/null
+++ b/src/test/java/seedu/address/model/appointment/AppointmentTest.java
@@ -0,0 +1,143 @@
+package seedu.address.model.appointment;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+
+public class AppointmentTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Appointment(null));
+ }
+
+ @Test
+ public void constructor_invalidAppointment_throwsIllegalArgumentException() {
+ String invalidAppointment = "";
+ assertThrows(IllegalArgumentException.class, () -> new Appointment(invalidAppointment));
+ }
+
+ @Test
+ public void isValidAppointment() {
+ // null Appointment number
+ assertThrows(NullPointerException.class, () -> Appointment.isValidAppointment(null));
+
+ // invalid Appointments
+ assertFalse(Appointment.isValidAppointment("")); // empty string
+ assertFalse(Appointment.isValidAppointment(" ")); // does not match HH:mm DAY
+ assertFalse(Appointment.isValidAppointment("FRI")); // times missing
+ assertFalse(Appointment.isValidAppointment("23:15-23:16")); // day missing
+ assertFalse(Appointment.isValidAppointment("3:15-4:30 FRI")); // H digit missing
+ assertFalse(Appointment.isValidAppointment("3:15-04:30 FRI")); // H digit missing
+ assertFalse(Appointment.isValidAppointment("12:34-13:33 FOB")); // not a day of the week
+ assertFalse(Appointment.isValidAppointment("00:00-00:12FRI")); // no space between times and day
+ assertFalse(Appointment.isValidAppointment("0000-1234 FRI")); // no colon between hour and minute
+ assertFalse(Appointment.isValidAppointment("23:59-24:59 MON")); // HH not in range 00-23
+ assertFalse(Appointment.isValidAppointment("22:60-23:40 MON")); // mm not in range 00-59
+ assertFalse(Appointment.isValidAppointment("25:61-26:20 MON")); // invalid range for HH and mm
+ assertFalse(Appointment.isValidAppointment("22:30-26:70 MON")); // invalid range for HH and mm
+ assertFalse(Appointment.isValidAppointment("23:00-22:00 MON")); // start time not before end time
+ assertFalse(Appointment.isValidAppointment("23:59-23:59 MON")); // start time equals end time
+
+ // valid Appointments
+ assertTrue(Appointment.isValidAppointment("13:59-14:00 TUE")); // matches HH:mm DAY
+ assertTrue(Appointment.isValidAppointment("03:59-04:59 WED")); // matches HH:mm DAY
+ assertTrue(Appointment.isValidAppointment("02:00-03:00 THU")); // matches HH:mm DAY
+ assertTrue(Appointment.isValidAppointment("12:00-13:00 FRI")); // matches HH:mm DAY
+ assertTrue(Appointment.isValidAppointment("13:30-14:00 SUN")); // matches HH:mm DAY
+ assertTrue(Appointment.isValidAppointment("03:15-04:30 fri")); // DAY can be in lower case
+ assertTrue(Appointment.isValidAppointment("03:15-04:30 fRI")); // DAY can be in lower and upper case
+ assertTrue(Appointment.isValidAppointment("03:15-04:30 Fri")); // DAY can be in lower and upper case
+ assertTrue(Appointment.isValidAppointment("03:15-04:30 fRi")); // DAY can be in lower and upper case
+
+ }
+
+ @Test
+ public void equals() {
+ Appointment appointment = new Appointment("13:30-14:00 SUN");
+
+ // same values -> returns true
+ assertTrue(appointment.equals(new Appointment("13:30-14:00 SUN")));
+
+ // same object -> returns true
+ assertTrue(appointment.equals(appointment));
+
+ // null -> returns false
+ assertFalse(appointment.equals(null));
+
+ // different types -> returns false
+ assertFalse(appointment.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(appointment.equals(new Appointment("13:30-14:30 FRI")));
+
+ // different values -> returns false
+ assertFalse(appointment.equals(new Appointment("13:30-14:30 SAT")));
+ }
+
+ @Test
+ public void overlapsWith_overlappingAppointment_returnsTrue() {
+ Appointment appointment = new Appointment("10:00-12:00 FRI");
+
+ Appointment overlappingAppointment = new Appointment("11:00-13:00 FRI");
+ assertTrue(appointment.overlapsWith(overlappingAppointment));
+ assertTrue(overlappingAppointment.overlapsWith(appointment));
+
+ overlappingAppointment = new Appointment("10:00-11:00 FRI");
+ assertTrue(appointment.overlapsWith(overlappingAppointment));
+ assertTrue(overlappingAppointment.overlapsWith(appointment));
+
+ overlappingAppointment = new Appointment("11:00-13:00 FRI");
+ assertTrue(appointment.overlapsWith(overlappingAppointment));
+ assertTrue(overlappingAppointment.overlapsWith(appointment));
+
+ overlappingAppointment = new Appointment("11:00-12:00 FRI");
+ assertTrue(appointment.overlapsWith(overlappingAppointment));
+ assertTrue(overlappingAppointment.overlapsWith(appointment));
+
+ overlappingAppointment = new Appointment("10:00-12:00 FRI");
+ assertTrue(appointment.overlapsWith(overlappingAppointment));
+ assertTrue(overlappingAppointment.overlapsWith(appointment));
+ }
+
+ @Test
+ public void overlapsWith_nonOverlappingAppointment_returnsFalse() {
+ Appointment appointment = new Appointment("10:00-12:00 FRI");
+ Appointment notOverlappingAppointment = new Appointment("12:00-14:00 FRI");
+
+ assertFalse(appointment.overlapsWith(notOverlappingAppointment));
+ assertFalse(notOverlappingAppointment.overlapsWith(appointment));
+
+ notOverlappingAppointment = new Appointment("10:00-12:00 SUN");
+
+ assertFalse(appointment.overlapsWith(notOverlappingAppointment));
+ assertFalse(notOverlappingAppointment.overlapsWith(appointment));
+ }
+
+ @Test
+ public void hasOverlapping_noOverlappingAppointments_returnsFalse() {
+ // Create a collection of non-overlapping appointments
+ Collection appointments = new ArrayList<>();
+ appointments.add(new Appointment("10:00-11:00 MON"));
+ appointments.add(new Appointment("12:00-13:00 MON"));
+ appointments.add(new Appointment("14:00-15:00 MON"));
+
+ // Check if there are any overlapping appointments
+ assertFalse(Appointment.hasOverlapping(appointments));
+ }
+
+ @Test
+ public void hasOverlapping_emptyCollection_returnsFalse() {
+ // Create an empty collection of appointments
+ Collection appointments = new ArrayList<>();
+
+ // Check if there are any overlapping appointments
+ assertFalse(Appointment.hasOverlapping(appointments));
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/appointment/DisjointAppointmentListTest.java b/src/test/java/seedu/address/model/appointment/DisjointAppointmentListTest.java
new file mode 100644
index 00000000000..53ceb721015
--- /dev/null
+++ b/src/test/java/seedu/address/model/appointment/DisjointAppointmentListTest.java
@@ -0,0 +1,142 @@
+package seedu.address.model.appointment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.appointment.exceptions.AppointmentNotFoundException;
+import seedu.address.model.appointment.exceptions.OverlappingAppointmentException;
+
+public class DisjointAppointmentListTest {
+
+ private final DisjointAppointmentList appointmentList = new DisjointAppointmentList();
+ private final Appointment fridayAppointment = new Appointment("10:00-12:00 FRI");
+ private final Appointment sundayAppointment = new Appointment("10:00-12:00 SUN");
+ private final Appointment sundayOverlappingAppointment = new Appointment("11:00-12:00 SUN");
+
+ @Test
+ public void contains_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.contains(null));
+ }
+
+ @Test
+ public void contains_appointmentNotInList_returnsFalse() {
+ assertFalse(appointmentList.contains(fridayAppointment));
+ }
+
+ @Test
+ public void contains_appointmentInList_returnsTrue() {
+ appointmentList.add(fridayAppointment);
+ assertTrue(appointmentList.contains(fridayAppointment));
+ }
+
+ @Test
+ public void add_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.add(null));
+ }
+
+ @Test
+ public void setAppointment_nullTargetAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointment(
+ null, sundayAppointment));
+ }
+
+ @Test
+ public void setAppointment_nullEditedAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointment(
+ sundayAppointment, null));
+ }
+
+ @Test
+ public void setAppointment_editedAppointmentIsDifferent_success() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.setAppointment(sundayAppointment, fridayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointment_targetAppointmentNotInList_throwsAppointmentNotFoundException() {
+ assertThrows(AppointmentNotFoundException.class, () -> appointmentList.setAppointment(
+ sundayOverlappingAppointment, sundayOverlappingAppointment));
+ }
+
+ @Test
+ public void remove_nullAppointment_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.remove(null));
+ }
+
+ @Test
+ public void remove_appointmentDoesNotExist_throwsAppointmentNotFoundException() {
+ assertThrows(AppointmentNotFoundException.class, () -> appointmentList.remove(sundayAppointment));
+ }
+
+ @Test
+ public void remove_existingAppointment_removesAppointment() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.remove(sundayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointments_nullAppointmentList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointments((AppointmentList) null));
+ }
+
+ @Test
+ public void setAppointments_appointmentList_replacesOwnListWithProvidedAppointmentList() {
+ appointmentList.add(fridayAppointment);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ appointmentList.setAppointments(expectedAppointmentList);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void setAppointments_nullList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> appointmentList.setAppointments((List) null));
+ }
+
+ @Test
+ public void setAppointments_list_replacesOwnListWithProvidedList() {
+ appointmentList.add(sundayAppointment);
+ List appointments = Collections.singletonList(fridayAppointment);
+ appointmentList.setAppointments(appointments);
+ AppointmentList expectedAppointmentList = new AppointmentList();
+ expectedAppointmentList.add(fridayAppointment);
+ assertEquals(expectedAppointmentList, appointmentList);
+ }
+
+ @Test
+ public void add_overlappingAppointment_throwsOverlappingAppointmentException() {
+ appointmentList.add(sundayAppointment); // Add an initial appointment
+ assertThrows(OverlappingAppointmentException.class, () -> appointmentList
+ .add(sundayOverlappingAppointment));
+ }
+
+ @Test
+ public void setAppointment_overlappingAppointment_throwsOverlappingAppointmentException() {
+ appointmentList.add(sundayAppointment);
+ appointmentList.add(new Appointment("12:00-14:00 SUN")); // Add an initial appointment
+ assertThrows(OverlappingAppointmentException.class, () -> appointmentList
+ .setAppointment(sundayAppointment, sundayOverlappingAppointment));
+ }
+
+ @Test
+ public void setAppointments_overlappingAppointment_throwsOverlappingAppointmentException() {
+ // Create a list with an overlapping appointment
+ List appointments = Arrays.asList(sundayAppointment, sundayOverlappingAppointment);
+ assertThrows(OverlappingAppointmentException.class, () -> appointmentList
+ .setAppointments(appointments));
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java
index 314885eca26..b8d32e868ad 100644
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ b/src/test/java/seedu/address/model/person/AddressTest.java
@@ -52,5 +52,13 @@ public void equals() {
// different values -> returns false
assertFalse(address.equals(new Address("Other Valid Address")));
+
+ // EmptyAddress -> returns false
+ assertFalse(address.equals(new EmptyAddress()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertFalse(new Address("dummy").isEmpty());
}
}
diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java
index f08cdff0a64..6d591382657 100644
--- a/src/test/java/seedu/address/model/person/EmailTest.java
+++ b/src/test/java/seedu/address/model/person/EmailTest.java
@@ -84,5 +84,13 @@ public void equals() {
// different values -> returns false
assertFalse(email.equals(new Email("other.valid@email")));
+
+ // EmptyEmail -> returns false
+ assertFalse(email.equals(new EmptyEmail()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertFalse(new Email("valid@email").isEmpty());
}
}
diff --git a/src/test/java/seedu/address/model/person/EmptyAddressTest.java b/src/test/java/seedu/address/model/person/EmptyAddressTest.java
new file mode 100644
index 00000000000..8ad530d13db
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/EmptyAddressTest.java
@@ -0,0 +1,37 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+public class EmptyAddressTest {
+
+ @Test
+ public void checkEquality() {
+ EmptyAddress emptyAddress = new EmptyAddress();
+ Address nonEmptyAddress = new Address("Harth");
+
+ // Another Empty address -> returns true
+ assertTrue(emptyAddress.equals(new EmptyAddress()));
+
+ // Itself -> returns true
+ assertTrue(emptyAddress.equals(emptyAddress));
+
+ // A non-empty address -> returns false
+ assertFalse(emptyAddress.equals(nonEmptyAddress));
+
+ // null -> returns false
+ assertFalse(emptyAddress.equals(null));
+
+ // Primitive data type -> returns false
+ assertFalse(emptyAddress.equals("Some words"));
+
+ // Non-primitive data type -> returns false
+ assertFalse(emptyAddress.equals(new EmptyPhone()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertTrue(new EmptyAddress().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/EmptyEmailTest.java b/src/test/java/seedu/address/model/person/EmptyEmailTest.java
new file mode 100644
index 00000000000..576a3338de5
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/EmptyEmailTest.java
@@ -0,0 +1,37 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+public class EmptyEmailTest {
+
+ @Test
+ public void checkEquality() {
+ EmptyEmail emptyEmail = new EmptyEmail();
+ Email nonEmptyEmail = new Email("test@example");
+
+ // Another Empty address -> returns true
+ assertTrue(emptyEmail.equals(new EmptyEmail()));
+
+ // Itself -> returns true
+ assertTrue(emptyEmail.equals(emptyEmail));
+
+ // A non-empty address -> returns false
+ assertFalse(emptyEmail.equals(nonEmptyEmail));
+
+ // null -> returns false
+ assertFalse(emptyEmail.equals(null));
+
+ // Primitive data type -> returns false
+ assertFalse(emptyEmail.equals("some words"));
+
+ // Non-primitive data type -> returns false
+ assertFalse(emptyEmail.equals(new EmptyPhone()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertTrue(new EmptyEmail().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/EmptyNoteTest.java b/src/test/java/seedu/address/model/person/EmptyNoteTest.java
new file mode 100644
index 00000000000..2aabb6942dc
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/EmptyNoteTest.java
@@ -0,0 +1,37 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+public class EmptyNoteTest {
+
+ @Test
+ public void checkEquality() {
+ EmptyNote emptyNote = new EmptyNote();
+ Note nonEmptyNote = new Note("Why");
+
+ // Another Empty address -> returns true
+ assertTrue(emptyNote.equals(new EmptyNote()));
+
+ // Itself -> returns true
+ assertTrue(emptyNote.equals(emptyNote));
+
+ // A non-empty address -> returns false
+ assertFalse(emptyNote.equals(nonEmptyNote));
+
+ // null -> returns false
+ assertFalse(emptyNote.equals(null));
+
+ // Primitive data type -> returns false
+ assertFalse(emptyNote.equals("Some words"));
+
+ // Non-primitive data type -> returns false
+ assertFalse(emptyNote.equals(new EmptyPhone()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertTrue(new EmptyNote().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/EmptyPhoneTest.java b/src/test/java/seedu/address/model/person/EmptyPhoneTest.java
new file mode 100644
index 00000000000..715e4b2f366
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/EmptyPhoneTest.java
@@ -0,0 +1,37 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+public class EmptyPhoneTest {
+
+ @Test
+ public void checkEquality() {
+ EmptyPhone emptyPhone = new EmptyPhone();
+ Phone nonEmptyPhone = new Phone("776");
+
+ // Another Empty address -> returns true
+ assertTrue(emptyPhone.equals(new EmptyPhone()));
+
+ // Itself -> returns true
+ assertTrue(emptyPhone.equals(emptyPhone));
+
+ // A non-empty address -> returns false
+ assertFalse(emptyPhone.equals(nonEmptyPhone));
+
+ // null -> returns false
+ assertFalse(emptyPhone.equals(null));
+
+ // Primitive data type -> returns false
+ assertFalse(emptyPhone.equals("Some Words"));
+
+ // Non-primitive data type -> returns false
+ assertFalse(emptyPhone.equals(new EmptyAddress()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertTrue(new EmptyPhone().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/LevelTest.java b/src/test/java/seedu/address/model/person/LevelTest.java
new file mode 100644
index 00000000000..9dfaf8827c9
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/LevelTest.java
@@ -0,0 +1,60 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class LevelTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Level(null));
+ }
+
+ @Test
+ public void constructor_invalidLevel_throwsIllegalArgumentException() {
+ String invalidLevel = "BOB";
+ assertThrows(IllegalArgumentException.class, () -> new Level(invalidLevel));
+ }
+
+ @Test
+ public void isValidLevel() {
+ // null level
+ assertThrows(NullPointerException.class, () -> Level.isValidLevel(null));
+
+ // invalid levels
+ assertFalse(Level.isValidLevel(""));
+ assertFalse(Level.isValidLevel("BOB"));
+
+ // valid levels
+ assertTrue(Level.isValidLevel("P1"));
+ assertTrue(Level.isValidLevel("P2"));
+ assertTrue(Level.isValidLevel("P3"));
+ assertTrue(Level.isValidLevel("P4"));
+ assertTrue(Level.isValidLevel("P5"));
+ assertTrue(Level.isValidLevel("P6"));
+ assertTrue(Level.isValidLevel("p1"));
+ }
+
+ @Test
+ public void equals() {
+ Level level = new Level("P1");
+
+ // same values -> returns true
+ assertTrue(level.equals(new Level("P1")));
+
+ // same object -> returns true
+ assertTrue(level.equals(level));
+
+ // null -> returns false
+ assertFalse(level.equals(null));
+
+ // different types -> returns false
+ assertFalse(level.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(level.equals(new Level("P2")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NoteTest.java b/src/test/java/seedu/address/model/person/NoteTest.java
new file mode 100644
index 00000000000..401f6f70b88
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/NoteTest.java
@@ -0,0 +1,39 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class NoteTest {
+
+ @Test
+ public void equals() {
+ Note note = new Note("Hello");
+
+ // same object -> returns true
+ assertTrue(note.equals(note));
+
+ // same values -> returns true
+ Note noteCopy = new Note(note.value);
+ assertTrue(note.equals(noteCopy));
+
+ // different types -> returns false
+ assertFalse(note.equals(1));
+
+ // null -> returns false
+ assertFalse(note.equals(null));
+
+ // different remark -> returns false
+ Note differentRemark = new Note("Bye");
+ assertFalse(note.equals(differentRemark));
+
+ // EmptyNote -> returns false
+ assertFalse(note.equals(new EmptyNote()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertFalse(new Note("dummy").isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..a588feba38f 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -6,14 +6,20 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.BOB;
+import java.util.ArrayList;
+import java.util.List;
+
import org.junit.jupiter.api.Test;
+import seedu.address.commons.util.StringUtil;
import seedu.address.testutil.PersonBuilder;
public class PersonTest {
@@ -34,16 +40,16 @@ public void isSamePerson() {
// same name, all other attributes different -> returns true
Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
- .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
+ .withAddress(VALID_ADDRESS_BOB).withNote(VALID_NOTE_BOB).withTags(VALID_TAG_HUSBAND).build();
assertTrue(ALICE.isSamePerson(editedAlice));
// different name, all other attributes same -> returns false
editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
assertFalse(ALICE.isSamePerson(editedAlice));
- // name differs in case, all other attributes same -> returns false
+ // name differs in case, all other attributes same -> returns true
Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build();
- assertFalse(BOB.isSamePerson(editedBob));
+ assertTrue(BOB.isSamePerson(editedBob));
// name has trailing spaces, all other attributes same -> returns false
String nameWithTrailingSpaces = VALID_NAME_BOB + " ";
@@ -85,15 +91,57 @@ public void equals() {
editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
assertFalse(ALICE.equals(editedAlice));
+ // different note -> returns false
+ editedAlice = new PersonBuilder(ALICE).withNote(VALID_NOTE_BOB).build();
+ assertFalse(ALICE.equals(editedAlice));
+
// different tags -> returns false
editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
assertFalse(ALICE.equals(editedAlice));
+
+ // no appointments -> returns false
+ assertFalse(ALICE.hasAppointments());
+
+ // has appointments -> returns true;
+ editedAlice = new PersonBuilder(ALICE).withAppointments("08:00-09:00 SUN").build();
+ assertTrue(editedAlice.hasAppointments());
}
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress()
+ + ", note=" + ALICE.getNote() + ", tags=" + ALICE.getTags()
+ + ", appointments=" + ALICE.getAppointments() + ", subjects="
+ + ALICE.getSubjects() + ", level=" + ALICE.getLevel() + "}";
assertEquals(expected, ALICE.toString());
}
+
+ @Test
+ public void getViewDetailsMethod() {
+ List expected = new ArrayList<>();
+ expected.add("ALICE PAULINE\n");
+ expected.add("\n[P2] [friends]\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nDETAILS:\n");
+ expected.add("94351253\n");
+ expected.add("alice@example.com\n");
+ expected.add("123, Jurong West Ave 6, #08-111\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nAPPOINTMENTS:\n");
+ expected.add("-\n");
+ expected.add(StringUtil.SEPARATOR);
+ expected.add("\nNOTES:\nShe likes aardvarks.");
+
+ assertEquals(expected.toString(), new PersonBuilder(ALICE).withLevel("P2").build()
+ .getViewDetails().toString());
+ }
+
+ @Test
+ public void getSummaryMethod() {
+ String expected = "\n[P6] [MATH] [owesMoney] [friends]\n";
+ String actual = new PersonBuilder(BENSON).withLevel("P6").build().getSummary();
+
+ assertEquals(expected, actual);
+ }
}
diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java
index deaaa5ba190..67ab58d6c56 100644
--- a/src/test/java/seedu/address/model/person/PhoneTest.java
+++ b/src/test/java/seedu/address/model/person/PhoneTest.java
@@ -56,5 +56,13 @@ public void equals() {
// different values -> returns false
assertFalse(phone.equals(new Phone("995")));
+
+ // EmptyPhone -> returns false
+ assertFalse(phone.equals(new EmptyPhone()));
+ }
+
+ @Test
+ public void checkEmptiness() {
+ assertFalse(new Phone("776").isEmpty());
}
}
diff --git a/src/test/java/seedu/address/model/person/SubjectTest.java b/src/test/java/seedu/address/model/person/SubjectTest.java
new file mode 100644
index 00000000000..7571b8db327
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/SubjectTest.java
@@ -0,0 +1,58 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class SubjectTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Subject(null));
+ }
+
+ @Test
+ public void constructor_invalidSubject_throwsIllegalArgumentException() {
+ String invalidSubject = "BOB";
+ assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubject));
+ }
+
+ @Test
+ public void isValidSubject() {
+ // null subject
+ assertThrows(NullPointerException.class, () -> Subject.isValidSubject(null));
+
+ // invalid subjects
+ assertFalse(Subject.isValidSubject(""));
+ assertFalse(Subject.isValidSubject("BOB"));
+
+ // valid subjects
+ assertTrue(Subject.isValidSubject("MATH"));
+ assertTrue(Subject.isValidSubject("ENGLISH"));
+ assertTrue(Subject.isValidSubject("SCIENCE"));
+ assertTrue(Subject.isValidSubject("math"));
+ assertTrue(Subject.isValidSubject("enGlish"));
+ }
+
+ @Test
+ public void equals() {
+ Subject subject = new Subject("MATH");
+
+ // same values -> returns true
+ assertTrue(subject.equals(new Subject("MATH")));
+
+ // same object -> returns true
+ assertTrue(subject.equals(subject));
+
+ // null -> returns false
+ assertFalse(subject.equals(null));
+
+ // different types -> returns false
+ assertFalse(subject.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(subject.equals(new Subject("ENGLISH")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..11a2c0d2696 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -47,6 +47,18 @@ public void contains_personWithSameIdentityFieldsInList_returnsTrue() {
assertTrue(uniquePersonList.contains(editedAlice));
}
+ @Test
+ public void findNearDuplicates_personWithSimilarName_returnsNearDuplicates() {
+ uniquePersonList.add(ALICE);
+
+ Person editedAlice = new PersonBuilder(ALICE)
+ .withName(ALICE.getName().toString().toLowerCase() + " ").build();
+
+ List nearDuplicates = uniquePersonList.findNearDuplicates(editedAlice);
+ assertEquals(1, nearDuplicates.size());
+ assertTrue(nearDuplicates.contains(ALICE.getName().toString()));
+ }
+
@Test
public void add_nullPerson_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> uniquePersonList.add(null));
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..8fcd6826085 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -4,6 +4,10 @@
import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.BENSON;
+import static seedu.address.testutil.TypicalPersons.BENSON_NO_ADDRESS;
+import static seedu.address.testutil.TypicalPersons.BENSON_NO_EMAIL;
+import static seedu.address.testutil.TypicalPersons.BENSON_NO_NOTE;
+import static seedu.address.testutil.TypicalPersons.BENSON_NO_PHONE;
import java.util.ArrayList;
import java.util.List;
@@ -23,14 +27,28 @@ public class JsonAdaptedPersonTest {
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_APPOINTMENT = "aaaaaaaaaa";
+ private static final String INVALID_SUBJECT = "dddddddd";
+ private static final String INVALID_LEVEL = "eeeeeeee";
private static final String VALID_NAME = BENSON.getName().toString();
private static final String VALID_PHONE = BENSON.getPhone().toString();
private static final String VALID_EMAIL = BENSON.getEmail().toString();
private static final String VALID_ADDRESS = BENSON.getAddress().toString();
+ private static final String VALID_NOTE = BENSON.getNote().toString();
+ private static final String VALID_LEVEL = BENSON.getLevel().toString();
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
+ private static final List VALID_APPOINTMENTS = BENSON.getAppointments()
+ .asUnmodifiableObservableList()
+ .stream()
+ .map(JsonAdaptedAppointment::new)
+ .collect(Collectors.toList());
+ private static final List VALID_SUBJECTS = BENSON.getSubjects().stream()
+ .map(JsonAdaptedSubject::new)
+ .collect(Collectors.toList());
+
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@@ -41,14 +59,17 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,46 +77,60 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ public void toModelType_nullPhone() throws Exception {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
+ assertEquals(BENSON_NO_PHONE, person.toModelType());
}
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ public void toModelType_nullEmail() throws Exception {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
+ assertEquals(BENSON_NO_EMAIL, person.toModelType());
}
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ public void toModelType_nullAddress() throws IllegalValueException {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_NOTE, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
+ assertEquals(BENSON_NO_ADDRESS, person.toModelType());
+ }
+
+ @Test
+ public void toModelType_nullNote() throws IllegalValueException {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, null, VALID_TAGS,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
+ assertEquals(BENSON_NO_NOTE, person.toModelType());
}
@Test
@@ -103,8 +138,18 @@ public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, invalidTags,
+ VALID_APPOINTMENTS, VALID_SUBJECTS, VALID_LEVEL);
assertThrows(IllegalValueException.class, person::toModelType);
}
+ @Test
+ public void toModelType_invalidAppointments_throwsIllegalValueException() {
+ List invalidAppointments = new ArrayList<>(VALID_APPOINTMENTS);
+ invalidAppointments.add(new JsonAdaptedAppointment(INVALID_APPOINTMENT));
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_NOTE, VALID_TAGS,
+ invalidAppointments, VALID_SUBJECTS, VALID_LEVEL);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
}
diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
index 4e5ce9200c8..cd44500f63c 100644
--- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
+++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
@@ -6,6 +6,11 @@
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.HOON;
import static seedu.address.testutil.TypicalPersons.IDA;
+import static seedu.address.testutil.TypicalPersons.JAGEN;
+import static seedu.address.testutil.TypicalPersons.KLEIN;
+import static seedu.address.testutil.TypicalPersons.LYON;
+import static seedu.address.testutil.TypicalPersons.MANFROY;
+import static seedu.address.testutil.TypicalPersons.NASIR;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import java.io.IOException;
@@ -103,6 +108,24 @@ private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) {
}
}
+ @Test
+ public void savingMissingFieldsToAddressBook_success() throws Exception {
+ Path filePath = testFolder.resolve("TempAddressBook.json");
+ AddressBook original = getTypicalAddressBook();
+ JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath);
+
+ // Each of the following persons have a missing field
+ original.addPerson(JAGEN);
+ original.addPerson(KLEIN);
+ original.addPerson(LYON);
+ original.addPerson(MANFROY);
+ // And this is missing all fields aside from Name
+ original.addPerson(NASIR);
+ jsonAddressBookStorage.saveAddressBook(original, filePath);
+ ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get();
+ assertEquals(original, new AddressBook(readBack));
+ }
+
@Test
public void saveAddressBook_nullFilePath_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null));
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..7aa9fe0d069 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -5,11 +5,16 @@
import java.util.stream.Stream;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.model.appointment.Appointment;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
/**
@@ -36,7 +41,9 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
descriptor.setAddress(person.getAddress());
+ descriptor.setNote(person.getNote());
descriptor.setTags(person.getTags());
+ descriptor.setAppointments(person.getAppointments());
}
/**
@@ -71,6 +78,22 @@ public EditPersonDescriptorBuilder withAddress(String address) {
return this;
}
+ /**
+ * Sets the {@code Note} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withNote(String note) {
+ descriptor.setNote(new Note(note));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Level} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withLevel(String level) {
+ descriptor.setLevel(new Level(level));
+ return this;
+ }
+
/**
* Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
@@ -81,6 +104,27 @@ public EditPersonDescriptorBuilder withTags(String... tags) {
return this;
}
+ /**
+ * Parses the {@code appointments} into a {@code AppointmentList} and set it to the {@code EditPersonDescriptor}
+ * that we are building.
+ */
+ public EditPersonDescriptorBuilder withAppointments(String... appointments) {
+ AppointmentList appointmentList = new AppointmentList();
+ appointmentList.addAll(Stream.of(appointments).map(Appointment::new).collect(Collectors.toList()));
+ descriptor.setAppointments(appointmentList);
+ return this;
+ }
+
+ /**
+ * Parses the {@code subjects} into a {@code Set} and set it to the {@code EditPersonDescriptor}
+ * that we are building.
+ */
+ public EditPersonDescriptorBuilder withSubjects(String... subjects) {
+ Set subjectSet = Stream.of(subjects).map(Subject::new).collect(Collectors.toSet());
+ descriptor.setSubjects(subjectSet);
+ return this;
+ }
+
public EditPersonDescriptor build() {
return descriptor;
}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..291a57e5f62 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -3,11 +3,20 @@
import java.util.HashSet;
import java.util.Set;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.EmptyAddress;
+import seedu.address.model.person.EmptyEmail;
+import seedu.address.model.person.EmptyLevel;
+import seedu.address.model.person.EmptyNote;
+import seedu.address.model.person.EmptyPhone;
+import seedu.address.model.person.Level;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Note;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
import seedu.address.model.tag.Tag;
import seedu.address.model.util.SampleDataUtil;
@@ -20,12 +29,18 @@ public class PersonBuilder {
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_NOTE = "She likes aardvarks.";
+ public static final String DEFAULT_LEVEL = "P1";
private Name name;
private Phone phone;
private Email email;
private Address address;
+ private Note note;
private Set tags;
+ private AppointmentList appointments;
+ private Set subjects;
+ private Level level;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -35,7 +50,11 @@ public PersonBuilder() {
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
+ note = new Note(DEFAULT_NOTE);
tags = new HashSet<>();
+ appointments = new AppointmentList();
+ subjects = new HashSet<>();
+ level = new Level(DEFAULT_LEVEL);
}
/**
@@ -46,7 +65,11 @@ public PersonBuilder(Person personToCopy) {
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
address = personToCopy.getAddress();
+ note = personToCopy.getNote();
tags = new HashSet<>(personToCopy.getTags());
+ appointments = personToCopy.getAppointments();
+ subjects = new HashSet<>(personToCopy.getSubjects());
+ level = personToCopy.getLevel();
}
/**
@@ -65,6 +88,24 @@ public PersonBuilder withTags(String ... tags) {
return this;
}
+ /**
+ * Parses the {@code appointments} into a {@code AppointmentList}
+ * and set it to the {@code Person} that we are building.
+ */
+ public PersonBuilder withAppointments(String ... appointments) {
+ this.appointments = SampleDataUtil.getAppointmentList(appointments);
+ return this;
+ }
+
+ /**
+ * Parses the {@code subjects} into a {@code Set}
+ * and set it to the {@code Person} that we are building.
+ */
+ public PersonBuilder withSubjects(String ... subjects) {
+ this.subjects = SampleDataUtil.getSubjectSet(subjects);
+ return this;
+ }
+
/**
* Sets the {@code Address} of the {@code Person} that we are building.
*/
@@ -89,8 +130,64 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code Note} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withNote(String note) {
+ this.note = new Note(note);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Level} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withLevel(String level) {
+ this.level = new Level(level);
+ return this;
+ }
+
+ /**
+ * Removes the {@code Phone} of the {@code Person} and replaces it with a {@code EmptyPhone}
+ */
+ public PersonBuilder removePhone() {
+ this.phone = new EmptyPhone();
+ return this;
+ }
+
+ /**
+ * Removes the {@code Email} of the {@code Person} and replaces it with a {@code EmptyEmail}
+ */
+ public PersonBuilder removeEmail() {
+ this.email = new EmptyEmail();
+ return this;
+ }
+
+ /**
+ * Removes the {@code Address} of the {@code Person} and replaces it with a {@code EmptyAddress}
+ */
+ public PersonBuilder removeAddress() {
+ this.address = new EmptyAddress();
+ return this;
+ }
+
+ /**
+ * Removes the {@code Note} of the {@code Person} and replaces it with a {@code EmptyNote}
+ */
+ public PersonBuilder removeNote() {
+ this.note = new EmptyNote();
+ return this;
+ }
+
+ /**
+ * Removes the {@code Level} of the {@code Person} and replaces it with a {@code EmptyLevel}
+ */
+ public PersonBuilder removeLevel() {
+ this.level = new EmptyLevel();
+ return this;
+ }
+
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, address, note, tags, appointments, subjects, level);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..24a8f569286 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,15 +1,20 @@
package seedu.address.testutil;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_APPOINTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Set;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.model.appointment.AppointmentList;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;
@@ -34,9 +39,17 @@ public static String getPersonDetails(Person person) {
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
+ sb.append(PREFIX_NOTE + person.getNote().value + " ");
+ sb.append(PREFIX_LEVEL + person.getLevel().toString() + " ");
person.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
+ person.getAppointments().asUnmodifiableObservableList().forEach(
+ s -> sb.append(PREFIX_APPOINTMENT + s.value + " ")
+ );
+ person.getSubjects().stream().forEach(
+ s -> sb.append(PREFIX_SUBJECT + s.getSubject() + " ")
+ );
return sb.toString();
}
@@ -49,6 +62,7 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
+ descriptor.getNote().ifPresent(note -> sb.append(PREFIX_NOTE).append(note.value).append(" "));
if (descriptor.getTags().isPresent()) {
Set tags = descriptor.getTags().get();
if (tags.isEmpty()) {
@@ -57,6 +71,15 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" "));
}
}
+ if (descriptor.getAppointments().isPresent()) {
+ sb.append(" ");
+ AppointmentList appointments = descriptor.getAppointments().get();
+ if (appointments.isEmpty()) {
+ sb.append(PREFIX_APPOINTMENT);
+ } else {
+ appointments.forEach(s -> sb.append(PREFIX_APPOINTMENT).append(s.value).append(" "));
+ }
+ }
return sb.toString();
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalAppointments.java b/src/test/java/seedu/address/testutil/TypicalAppointments.java
new file mode 100644
index 00000000000..e783cb21df3
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalAppointments.java
@@ -0,0 +1,13 @@
+package seedu.address.testutil;
+
+import seedu.address.model.appointment.Appointment;
+
+/**
+ * A utility class containing a list of {@code Appointment} objects to be used in tests.
+ */
+public class TypicalAppointments {
+ public static final Appointment SUN_APPOINTMENT_10_TO_12 = new Appointment("10:00-12:00 SUN");
+ public static final Appointment SUN_APPOINTMENT_11_TO_13 = new Appointment("11:00-13:00 SUN");
+ public static final Appointment SAT_APPOINTMENT_10_TO_12 = new Appointment("10:00-12:00 SAT");
+ public static final Appointment FRI_APPOINTMENT_10_TO_12 = new Appointment("10:00-12:00 FRI");
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..8c28ec13a22 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -2,12 +2,25 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_FRIDAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_APPOINTMENT_SUNDAY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_LEVEL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_LEVEL_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_CELINE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_CELINE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -26,11 +39,18 @@ public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
.withPhone("94351253")
- .withTags("friends").build();
+ .withTags("friends").withNote("She likes aardvarks.").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
+ .withTags("owesMoney", "friends").withNote("He can't take beer!")
+ .withAppointments("12:00-13:00 SUN")
+ .withSubjects("MATH").build();
+ public static final Person BENSON_NO_ADDRESS = new PersonBuilder(BENSON).removeAddress().build();
+ public static final Person BENSON_NO_PHONE = new PersonBuilder(BENSON).removePhone().build();
+ public static final Person BENSON_NO_EMAIL = new PersonBuilder(BENSON).removeEmail().build();
+ public static final Person BENSON_NO_NOTE = new PersonBuilder(BENSON).removeNote().build();
+
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
.withEmail("heinz@example.com").withAddress("wall street").build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
@@ -48,12 +68,41 @@ public class TypicalPersons {
public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
.withEmail("hans@example.com").withAddress("chicago ave").build();
+ // More persons; these are missing one or more fields for testing purposes
+ public static final Person JAGEN = new PersonBuilder().withName("Jagen Arch")
+ .withEmail("jagen@example.com").withAddress("japan").withNote("Has Red Paint").build();
+ public static final Person KLEIN = new PersonBuilder().withName("Klein Elibe").withPhone("77617761")
+ .withAddress("america").withNote("Has Sister").build();
+ public static final Person LYON = new PersonBuilder().withName("Lyon Mag").withPhone("77617761")
+ .withEmail("lyon@example.com").withNote("Careful around stones").build();
+ public static final Person MANFROY = new PersonBuilder().withName("Manfroy Jug").withPhone("77617761")
+ .withAddress("manfroy@example.com").withAddress("big map").build();
+ public static final Person NASIR = new PersonBuilder().withName("Nasir Tell").build();
+
// Manually added - Person's details found in {@code CommandTestUtil}
public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withNote(VALID_NOTE_AMY)
+ .withTags(VALID_TAG_FRIEND).build();
public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withNote(VALID_NOTE_BOB)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withSubjects(VALID_SUBJECT_BOB).withLevel(VALID_LEVEL_BOB).build();
+ public static final Person CELINE = new PersonBuilder().withName(VALID_NAME_CELINE).withPhone(VALID_PHONE_CELINE)
+ .withEmail(VALID_EMAIL_CELINE).withAddress(VALID_ADDRESS_CELINE).withNote(VALID_NOTE_CELINE)
+ .withAppointments(VALID_APPOINTMENT_FRIDAY, VALID_APPOINTMENT_SUNDAY)
+ .withSubjects(VALID_SUBJECT_CELINE).withLevel(VALID_LEVEL_CELINE)
.build();
+ public static final Person NO_ADDRESS_AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
+ .withEmail(VALID_EMAIL_AMY).withNote(VALID_NOTE_AMY).withTags(VALID_TAG_FRIEND).removeAddress().build();
+ public static final Person NO_PHONE_AMY = new PersonBuilder().withName(VALID_NAME_AMY)
+ .withEmail(VALID_EMAIL_AMY).withNote(VALID_NOTE_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withTags(VALID_TAG_FRIEND).removePhone().build();
+ public static final Person NO_EMAIL_AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
+ .withAddress(VALID_ADDRESS_AMY).withNote(VALID_NOTE_AMY).withTags(VALID_TAG_FRIEND).removeEmail().build();
+ public static final Person NO_NOTE_AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).removeNote().build();
+ public static final Person NAME_ONLY_CELINE = new PersonBuilder().withName(VALID_NAME_CELINE)
+ .removeEmail().removeNote().removeAddress().removePhone().removeLevel().build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER