diff --git a/.gitignore b/.gitignore index 823d175eb670..c8e31b0cdb8a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ lib/* *.log *.log.* *.csv -config.json +/config.json src/test/data/sandbox/ preferences.json .DS_Store diff --git a/README.adoc b/README.adoc index 0ad72da1a5ba..2584763fcb28 100644 --- a/README.adoc +++ b/README.adoc @@ -1,27 +1,26 @@ -= Address Book (Level 4) += ProManage ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/nusCS2113-AY1819S1/addressbook-level4[image:https://travis-ci.org/nusCS2113-AY1819S1/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2113-AY1819S1-T16-2/main[image:https://travis-ci.org/CS2113-AY1819S1-T16-2/main.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2113-AY1819S1-T16-2/main[image:https://coveralls.io/repos/github/CS2113-AY1819S1-T16-2/main/badge.svg?branch=master[Coverage Status]] +https://ci.appveyor.com/project/jieliangang/main[image:https://ci.appveyor.com/api/projects/status/49aw1utcdqcq2tjo?svg=true[Build status]] ifdef::env-github[] -image::docs/images/Ui.png[width="600"] +image::docs/images/Ui.png[width="800"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::images/Ui.png[width="800"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Event Management application designed for project-based companies. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* We provide an all-in-one platform that saves our consumers’ time and money. Our customers will feel at ease communicating with one another in their project team setting. +* Managers of project team are now able to oversee their employees and overall project easily and conveniently. + +* Features: +** Employees management. +** Department management. +** Event management. == Site Map @@ -33,8 +32,7 @@ endif::[] == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. +* Based of https://github.com/se-edu/addressbook-level4[AddressBook-Level4 project] created by the https://github.com/se-edu/[SE-EDU initiative] * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..4fdfdeb86614 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "jieliangang", + "displayName": "ANG...ANG", + "authorNames": ["jieliangang", "jlang", "Jie Liang Ang"] + }, + { + "githubId": "fr3ddy4", + "displayName": "FAR...LAN", + "authorNames": ["fr3ddy4", "Ruslan Farkhutdinov"] + }, + { + "githubId": "Jeevz10", + "displayName": "JEE...HAR", + "authorNames": ["Jeevz10"] + }, + { + "githubId": "IcedCoffeeBoy", + "displayName": "LEE...ANG", + "authorNames": ["IcedCoffeeBoy","DESKTOP-T5GT484\\user", "Ming Liang", "Lee Ming Liang"] + }, + { + "githubId": "perrythewang", + "displayName": "PER...ING", + "authorNames": ["perrythewang", "Perry Wang"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..8d6df41d48cf 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'proManage.jar' destinationDir = file("${buildDir}/jar/") } @@ -207,8 +207,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', + 'site-name': 'ProManage', + 'site-githuburl': 'https://github.com/CS2113-AY1819S1-T16-2/main', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..c7dd6593688c 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,56 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +ProManage was developed by the https://github.com/orgs/CS2113-AY1819S1-T16-2/teams/developers[T16-2] team. + + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Lee Ming Liang +image::icedcoffeeboy.png[width="150", align="left"] +{empty}[https://github.com/IcedCoffeeBoy[github]] [<>] -Role: Project Advisor +**Role:** Developer + +**Responsibilities:** In charge of Storage, Code Quality ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] -Role: Team Lead + -Responsibilities: UI +=== Ang Jie Liang +image::jieliangang.png[width="150", align="left"] +{empty}[https://github.com/jieliangang[github]] [<>] + +**Role:** Team Lead + +**Responsibilities:** Integration, Scheduling and Tracking, Deliverable and Deadlines ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] -Role: Developer + -Responsibilities: Data +=== Jeevan Neralakere Somashekhar +image::jeevz10.png[width="150", align="left"] +{empty}[https://github.com/Jeevz10[github]] [<>] + +**Role:** Developer + +**Responsibilities:** Documentation, Testing + ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Perry Wang Zhi Ming +image::perrythewang.png[width="150", align="left"] +{empty}[https://github.com/perrythewang[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +**Role:** Developer + +**Responsibilities:** In Charge of Model ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Ruslan Farkhutdinov +image::fr3ddy4.png[width="150", align="left"] +{empty}[https://github.com/fr3ddy4[github]] [<>] -Role: Developer + -Responsibilities: UI +**Role:** Developer + +**Responsibilities:** In charge of UI ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..1e3bee2860db 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,5 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2113-AY1819S1-T16-2/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..27f91945dba9 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += ProManage - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,8 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +:repoURL: https://github.com/CS2113-AY1819S1-T16-2/main +By: `T16-2`      Since: `September 2018`      Licence: `NUS` == Setting up @@ -159,16 +158,18 @@ Note how the event is propagated through the `EventsCenter` to the `Storage` and The sections below give more details of each component. +// tag::Ui[] [[Design-Ui]] === UI component .Structure of the UI Component -image::UiClassDiagram.png[width="800"] +image::UiClassDiagram.png[width="500"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `EventListPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +// end::Ui[] The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -177,6 +178,7 @@ The `UI` component, * Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. * Responds to events raised from various parts of the App and updates the UI accordingly. + [[Design-Logic]] === Logic component @@ -187,7 +189,7 @@ image::LogicClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `ProManageParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. . The command execution can affect the `Model` (e.g. adding a person) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. @@ -197,19 +199,41 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen .Interactions Inside the Logic Component for the `delete 1` Command image::DeletePersonSdForLogic.png[width="800"] +// tag::Parser[] +[[Design-Parser]] +=== Parser component +[[fig-ParserClassDiagram]] +.Structure of the Parser Component +image::ParserClassDiagram.png[width="800"] + +*API* : +link:{repoURL}/src/main/java/seedu/address/logic/parser/ProManageParser.java[`ProManageParser.java`] + +. `ProManageParser` use children class of `CommandParser` (eg. ManagerParser) to parse the user command. +. When login/logout command is executed, the CommandParser object within ProManageParser is changed accordingly to DefaultParser, +ManagerParser or EmployeeParser. +. DefaultParser, ManagerParser or EmployeeParser are subclass of the abstract class CommandParser +. This display the behaviour of polymorphism as ProManageParser will always pass the input into CommandParser +but its behaviour depends on implementation of its subclass. +. Each of it subclass will only know commands that is according to the privilege level. +// end::Parser[] + + +// tag::Model[] [[Design-Model]] === Model component .Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelClassDiagram.png[width="620"] +// end::Model[] *API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Address Book and Event List data. +* exposes an unmodifiable `ObservableList` and `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. [NOTE] @@ -217,18 +241,20 @@ As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` + image:ModelClassBetterOopDiagram.png[width="800"] +// tag::Storage[] [[Design-Storage]] === Storage component .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] +// end::Storage[] *API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the Address Book and Event List data in xml format and read it back. [[Design-Commons]] === Common classes @@ -239,92 +265,413 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== Undo/Redo feature + +// tag::Sort[] + +=== Event Sorting + ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +The sort mechanism is facilitated by Comparator. +When the sort method for FXObservableList is called, it will take a Comparator object to be use for sorting the list. +The comparator is able to take in two Event class objects and compare the relative parameter values. +The parameter can be `EventName`, `Date` & `StartTime`. +To allow sorting of these parameters, there are three types of comparators. -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +image::SortNewCommand1StateListDiagram.png[width="650"] -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +image::SortSequenceDiagram.png[width="650"] -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +==== Design Considerations -image::UndoRedoStartingStateListDiagram.png[width="800"] +===== Committing Event List after sorting + * **Alternative 1 (current choice):** Commits and saves the entire event list. +** Pros: Easy to implement and able to use undo to the previous state. +** Cons: May have performance issues in terms of memory usage. +* **Alternative 2:** Does not commit the event list. +** Pros: Will use less memory. +** Cons: Unable to use undo function to revert back to the previous view. -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +// end::Sort[] -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +// tag::Invite[] +=== Invite feature -[NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +==== Current Implementation -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +The `invite` command allows users to add attendees to an existing event, which is represented by an `Event` object. +The command currently only allows users to add a single person to an event at a time, based on the indices on the UI. +This command adds the to-be-invited `Person` 's email, which is unique, to the chosen `Event`, so that the attendees of a particular event is recorded. -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +The adding of attendees is facilitated by the class: `Attendees`, which consists of a list of emails of type `String`. -[NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +.Class Diagram of Event and Attendee class +image::EventAndAttendeeClassDiagram.png[width="800"] -The following sequence diagram shows how the undo operation works: +The following is a more detailed description of the `Attendee` class: -image::UndoRedoSequenceDiagram.png[width="800"] +* `Attendees` +** Each `Event` object consist of an `Attendees` object, which is a list of the emails of the different `Person` attending the event. +** This class is a wrapper class around the internal representation of a `Set` of emails. +** Only the email of the `Person` is recorded in an `Event`, as the email should be unique and uneditable. + +Storing only the *email* of `Person` in the `Attendees` object of `Event` saves memory storage and facilitates the `select`, `viewmine` and `selectEvent` feature. +As we select a person, we can simply iterate through the event list and check whether the person's email is in the attendees list. +Similarly, as we select an event, we can first obtain the list of emails from the `Attendees`, and filter the employee list accordingly. + +This command only executes successfully if the event does not not clash with the person's schedule. + +Given below is an illustration to describe the detection of whether two events clash, which is facilitated by the method `hasClash` in `Event`: + +.Logic behind `Event#hasClash` +image::EventClashDiagram.png[width="450"] + +Thus, to check whether the event clashes with an employee's schedule, we iterate through the `ObservableList` and check whether the `Event` 1) contains the employee's email in the `Attendee` and 2) clashes with the selected event. + +**** +`Event#hasClash` is widely used in other features such as `addEvent` and `editEvent` to ensure that + +1. Different events are not held at the same location and time. + +2. An employee do not have two events he/she needs to attend at the same time. +**** + +Implementation of `RemoveCommand` is similar to `InviteCommand`, but removes persons from `Attendees` of events instead. + +==== Execution of Command + +Given below is an example usage scenario and how the invite mechanism works. + +For example, the user inputs `invite 1 to/1` to invite the 1st person in the address book to the 1st event in the event list. + +Step 1. The command text is passed to an instance of the `LogicManager` class. + +Step 2. The `LogicManager` instance calls `ProManageParser#parseCommand`, which parses the invite command phrase. + +Step 3. `InviteCommandParser#parse` parses the person and event index. An instance of the `InviteCommand` encapsulating the two indices information is then returned after parsing. + +Step 4. `Logic Manager` then executes this `InviteCommand` by calling `InviteCommand#execute`. + +Step 5. The filtered person list and event list is first obtained by calling `PersonModel#getFilteredPersonList` and `EventModel#getFilteredEventList`. Based on the indices, the `Person` and `Event` is selected. Then, the `Person` object's email is obtained by calling `Person#getEmail` + +Step 6. Two steps of verification is required before the person is invited to the event. + +* First, `Event#hasAttendee` checks whether the person is already invited to the event. +* Second, `Model#hasClash` checks whether the event clashes with the person's schedule. + +Step 7. After successful verification, the person's email is added to the obtained `Attendees` object. The new `Attendees` object is then added to a new copy of the `Event` object. + +Step 8. The new `Event` object is updated to the model by calling `Model#updateEvent`. + +Step 9. The `InviteCommand#execute` finally returns a `CommandResult` with a success message. + +The sequence diagram below illustrates the execution of the `invite` command. + +image::InviteSequenceDiagram.png[width="700"] + +==== Design Consideration + +===== Aspect: How to link `Person` and `Event` + +* **Alternative 1 (current choice):** Saves only the emails of the Persons in the Attendees of Events. +** Pros: Saves space. Able to filter EventList easily. +** Cons: Requires going through the entire list of EventList to obtain details of events attended by the person. + +* **Alternative 2:** Saves the entire information of Events for each Persons. +** Pros: Fast access to information of Events attended by any Persons. +** Cons: Takes up a lot of storage space and memory. Many repeated items within the storage files. + +===== Aspect: Data structure to support `invite` command + +* **Alternative 1 (current choice):** Use `HashSet` to store persons emails. +** Pros: Easier and faster to retrieve or validate names stored. Able to handle duplicates easily. +** Cons: Sequence of persons added is lost. Currently, this feature is not important for the project. + +* **Alternative 2:** Use `ArrayList` to store persons emails. +** Pros: Emails are stored in order of addition. +** Cons: Inefficient when handling data. + +==== Future Improvements + +===== Adding entire department or multiple persons to an event + +The current `invite` command only invites one person to a chosen event based on the index chosen. This feature can be further extended to enable inviting more than one person or an entire department to an event. +One possible way to to implement this is to have the user + +* input a certain range (2-5) or +* multiple indices (1,3,6,7,8) or +* input the department name + +The same concept can also be applied to events, being able to invite one person to multiple events with one single command. + +Implementation of this additional feature would require changes to both `InviteCommandParser` and `InviteCommand#execute`. Additionally, handling of clashing events needs to be considered as it now involves more than one participant and event. + +Similar improvements can be made to `remove` command, being able to remove a person from a number of events with one single command. +// end::Invite[] + +// tag::Select[] +=== Select feature + +==== Current Implementation +The `select` command allows users to view the event schedules of an employee on a certain date, month, and year, or all of his/her events. +The command currently only allows users to view the event of a single employee, based on the indices of the employee list on the UI. +This command only executes successfully if the input index is valid (not out of bound). + +The command is facilitated by the class: `Attendees`, a `HashSet` of emails of type `String`, as described in Section 3.2.1. + +==== Execution of Command +Given below is an example usage scenario and how the `select` mechanism works. + +For example, the user inputs `select 1 y/2018 m/06` to select the 1st person in the address book and view his/her event schedule on June 2018. + +* The command text is passed to an instance of the `LogicManager` class. + +* The `LogicManager` instance calls `ProManageParser#parsecommand`, which parses the `select` command phrase. + +* `InviteCommandParser#parse` parses the person and date parameter(optional). Depending on whether the date parameter is inputted, an instance of the `SelectCommand` encapsulating the relevant information is then returned. + +* `Logic Manager` then executes this `SelectCommand` by calling `SelectCommand#execute`. + +* The filtered person list is first obtained by calling `PersonModel#getFilteredPersonList`. Based on the index, the `Person` is selected. The `Person` object's email is obtained by calling `Person#getEmail` + +* A `Predicate` will be constructed to generate the filtered event list. +** If the user chose to view all the events of the person (e.g. `select 1`), an `AttendeeContainsEmailPredicate` object will be instantiated. +** If the user chose to view the events of the person on a certain time period (e.g. `select 1 y/2018 m/12`), an `EventContainsAttendeeAndDatePredicate` object will be instantiated. + +* The filtered event list is then updated by calling `Model#updateFilteredEventList` with the predicate. + +* The filtered event list is then sorted by calling `Model#sortByDate`. + +* The `SelectCommand#execute` finally returns a `CommandResult` with a success message. + +The sequence diagram below illustrates the execution of the `invite` command. + +image::SelectSequenceDiagram.png[width="700"] + +// end::Select[] + +// tag::FindEvent[] +=== Find Event feature + +==== Current Implementation +The `findEvent` allows the user to search for an event using some keywords. It searches for such events which contain at least +one of keywords in their names or description. + + +==== Execution of Command +Given below is an example usage scenario and how the `findEvent` mechanism works. + +Step 1. The user inputs `findEvent meeting` to find the event containing the word meeting in its name or description. + +Step 2. The command is parsed and the result is shown. + + +// end::FindEvent[] + +// tag::List[] +=== List Feature + +==== Current Implementation + +The `list` feature allows users to filter through all the individual people in ProManage and understand which department +they are from. The command currently has 4 sub-features; `list all` , `list all people`, `list all events` and `list dep DEPARTMENT`. The user can list all the +people and events in ProManage by simply typing `list all`. Alternatively, the user can filter through ProManage and get the +relevant Person's information by listing those of the specified department. The user can list multiple departments such +as `list dep Admin Finance`. As of now, each person can only be inside one department. + +The listing of people from the respective departments is facilitated by the class: `Departments`, +a list of departments of type `String`. + +The following is a more detailed description of the classes involved: + +* `Department` +** Each `Person` object has a `Department`, which is the department in which the person is in. +** This class is essentially another piece of information about the person. +** Each `Person` can only be in one `Department`. + +==== Execution of Command + +Given below is an example usage scenario and how the list mechanism works. + +Step 1. The user executes `list dep Admin` to list only the people in the Admin department. The command text is passed +to an instance of the `LogicManager` class. + +Step 2. The `LogicManager` instance calls `ProManage#parsecommand`, which parses the `list` command prefix +"list". + +Step 3. `ListCommandParser#parse` parses the type of command that is called upon and if applicable, the departments +listed. The list command can take in either `all`, `all people` , `all events` or `dep`. `all` means that are no predicates and 'dep' means specific +departments are about to be listed. An instance of the `ListCommand` encapsulating the type of `ListCommand` and if +applicable, the predicates involved, is then returned to `Logic Manager`. + +Step 4. `Logic Manager` then executes this `ListCommand` by calling `ListCommand#execute`. + +Step 5. The filtered person list is first obtained by calling `PersonModel#updateFilteredPersonList`. Based on the departments, the list of `Person` are selected. + +Step 9. The `ListCommand#execute` finally returns a `CommandResult` with a success message. + +The sequence diagram below illustrates the execution of the `list` command. + +image::ListSequenceDiagram.png[width="800"] + +==== Future Improvements + +===== Listing people who stay in the same town + +The current `list` command does not utilize the address of each person. Thus, to make data more easily accessible, ProManage can implement Google Maps API and mark out where each employee stays on the map. -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +* Utilize Google Maps API and store the map - `list map` + +===== Listing people's birthdays + +The current person has no date of birth attribute. Thus, a date of birth attribute could be implemented. On top of that, ProManage can display the current date and time. Along with this, +it can list out the upcoming birthdays of the personnel in the company. + +* Implement date of birth attribute and display current date and time the moment the application is launched. List out upcoming birthdays - `list birthdays` +// end::List[] + +// tag::UndoRedo[] +=== Undo/Redo Feature + +==== Current Implementation + +The `undo` and `redo` feature allows users to undo and redo their previous commands. The commands that can +be undone are only those that modify the contents of the address book or event list, such as adding a person +or event within the app. This is managed within `ModelManager`. + +The undo/redo mechanism is implemented separately for the address book and event list by the +`VersionedAddressBook` and `VersionedEventList`. They are extensions of the superclass `AddressBook` and +`EventList` respectively with a history of states stored internally as an `addressBookStateList` +for the address book, `eventListStateList` for the event list, and a `currentStatePointer` in each of them. +To ensure that the address book and event list are undone/redone correctly, a `StateHistoryList` (sub-class +of `LinkedList`) is kept in the `ModelManager` to keep track of the list that was modified. + +The following operations were implemented: + +* `VersionedAddressBook#commit()` — Saves the current address book state in its internal history. + +* `VersionedAddressBook#undo()` — Restores the previous address book state from its internal history. + +* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its internal history. + +* `VersionedEventList#commit()` — Saves the current event list in its internal history. + +* `VersionedEventList#undo()` — Restores the previous event list state from its internal history. + +* `VersionedEventList#redo()` — Restores a previously undone event list state from its internal history. + +These operations are exposed in the Model interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` +and `Model#redoAddressBook()` for the address book, and `Model#commitEventList()`, `Model#undoEventList()` +and `Model#redoEventList()` for the event list. Two wrapper methods, `Model#undo()` and `Model#redo()` +are also implemented. These will be the methods called by the commands `undo` and `redo`. + +The `StateHistoryList` implements the following operations for the `Model` to decide whether to +undo/redo the address book or event list: + +* `StateHistoryList#getCurrentState()` - Retrieves the latest record state. + +* `StateHistoryList#getNextState()` - Retrieves the next (previously undone) record state. + +* `StateHistoryList#decrementPointer()` - Shifts record state pointer back. Called during `undo`. + +* `StateHistoryList#incrementPointer()` - Shifts record state pointer forward. Called during `redo`. + +A command can either modify the address book, event list, or both. Within the model, the `StateHistoryList` +keeps track of all model methods which modify the address book or event list, and stores a history +record value within itself for each method called. + +When undoing a command, the `ModelManager` requests for the latest record by `StateHistoryList#getCurrentState()` +and calls the corresponding `VersionedAddressBook#undo()` and/or `VersionedEventList#undo()`. The ModelManager +also informs the `StateHistoryList` that an undo command was issued to shift the pointer back. + +When redoing a command,the `ModelManager` requests for the next record from `StateHistoryList` and +calls the corresponding `VersionedAddressBook#redo()` and/or `VersionedEventList#redo()`. The ModelManager +also informs the `StateHistoryList` that an undo command was issued to shift the pointer forward. + +Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. + +Step 1. The user launches the application for the first time. The VersionedAddressBook and VersionedEventList +will be initialized with the initial address book and event list states, and the currentStatePointers will +point to their single state respectively. The StateHistoryList is instantiated as an empty list. + +image::UndoRedoModelInitializationStateListDiagram.png[width="800"] + +Step 2. The user executes `add` (with valid input arguments) to add a person to the address book. The `add` command calls +`Model#addPerson()`, which will add a state record index integer `STATE_ADDRESSBOOK` to the `StateHistoryList`. +This command is the last entered command, therefore the `pointer` in `StateHistoryList` shifts forward. +The `add` command also calls `Model#commitAddressBook()`, causing the modified state of the address book after +the `add` command executes to be saved in the `addressBookStateList`, and the address book `currentStatePointer` +is shifted to the newly inserted address book state. The `currentStatePointer` for `VersionedEventList` remains unchanged +as only the `VersionedAddressBook` was modified.. + +image::UndoRedoNewCommandAB1StateListDiagram.png[width="800"] [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +If a command fails its execution, it will not call `Model#commitAddressBook()` or `Model#commitEventList()`, +so the address book or event list state will not be saved into `addressBookStateList` or `eventListStateList`. +The `StateHistoryList` will also not save a record state. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 3. The user realises that they have accidentally added the wrong person, and decides to undo the action by +executing the `undo` command. The `undo` command calls `Model#undo()`, which gets the latest record state +from `StateHistoryList` and recognises that the `VersionedAddressBook` was modified in the previous command. +The `Model#undoAddressBook()` is then called, which shift the currentStatePointer once to the left, pointing it to +the previous address book state, and restores the address book to that state. Also, the `pointer` in `StateHistoryList` +is shifted to the left by `StateHistoryList#decrementPointer`. -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] +image::UndoRedoNewCommandAB2StateListDiagram.png[width="800"] -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +[NOTE] +If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous +address book states to restore. The undo command uses `Model#canUndo()` to check if this is the case. +If so, it will return an error to the user rather than attempting to perform the undo. + -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] +The following sequence diagram shows how the undo operation works: -The following activity diagram summarizes what happens when a user executes a new command: +image::UndoRedoSequenceDiagram.png[width="800"] -image::UndoRedoActivityDiagram.png[width="650"] +Step 4. +The redo command does the opposite — it calls `Model#redo()`, which shifts the `currentStatePointer` in either +the `VersionedAddressBook` and/or `VersionedEventList` once to the right, pointing to the previously +undone state, and restores it to that state. -==== Design Considerations +[NOTE] +If the `pointer` is at index `stateHistoryList.size() - 1`, pointing to the latest memory state, then +there are no undone states to restore. The redo command uses `Model#canRedo()` to check if this is the case. +If so, it will return an error to the user rather than attempting to perform the redo. -===== Aspect: How undo & redo executes +image::UndoRedoNewCommandAB3StateListDiagram.png[width="800"] -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +Step 5. +The user then decides to add a new event to the event list, by the `addEvent` command. The `addEvent` command calls +`Model#addEvent()`, which will add a state record index integer `STATE_EVENTLIST` to the `StateHistoryList`. +This command is the last entered command, therefore the `pointer` in `StateHistoryList` shifts forward. +The `addEvent` command also calls `Model#commitEventList()`, causing the modified state of the event list after +the `addEvent` command executes to be saved in the `eventListStateList`, and the event list `currentStatePointer` +is shifted to the newly inserted address book state. The `currentStatePointer` for `VersionedAddressBook` remains unchanged +as only the `VersionedEventList` was modified. + +image::UndoRedoNewCommandELStateListDiagram.png[width="800"] -===== Aspect: Data structure to support the undo/redo commands +Step 6. +The user then decides to execute `clear`. Since this command must clear both the address book and event list, +the `clear` command calls both `Model#commitAddressBook()` and `Model#commitEventList()`. It adds a state +record index integer `STATE_BOTH` to the `StateHistoryList`. The `currentStatePointer` in both +`VersionedAddressBook` and `VersionedEventList` shifts to point to the newly added states respectively. -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +image::UndoRedoNewCommandBothStateListDiagram.png[width="800"] -// tag::dataencryption[] -=== [Proposed] Data Encryption +==== Future Improvements -_{Explain here how the data encryption feature will be implemented}_ +===== Façade Class for VersionedAddressBook and VersionedEventList -// end::dataencryption[] +To improve abstraction and shift the responsibility of deciding which list to undo or redo away from the +`Model`, a façade class can be created, e.g. `VersionedProManage`, to hold the `StateHistoryList`, +`VersionedAddressBook` and `VersionedEventList`. The model will then only have to call the `VersionedProManage#undo()` +or `VersionedProManage#redo()` methods. +// end::UndoRedo[] === Logging @@ -532,363 +879,486 @@ A project often depends on third-party libraries. For example, Address Book depe a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] +// tag::productscope[] [appendix] -== Suggested Programming Tasks to Get Started +== Product Scope -Suggested path for new programmers: +*Target user profile*: -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +* Companies have project management teams. Delegation of tasks and events can become complicated. +* Project teams comprises of project managers, and the various sub branches : Admin, Logistics, Programmes, Publicity, Marketing and Safety. +* This application aims to provide an all in one platform to ease the mode of task and event allocation. +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +**Project Manager:** -[[GetStartedProgramming-EachComponent]] -=== Improving each component +* In charge of overall project. +* He/She has the autonomy to add/edit/view/delete all of the events and tasks of his/her employees. -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +**Employees:** -[discrete] -==== `Logic` component + * He/she is only entitled to view all of the events of himself/herself within + the department. -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +*Value proposition*: -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +* Facilitates workflow faster than a typical mouse/GUI driven app. +* Saves consumers' efficiency, money and time tremendously. -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +[appendix] +// end::productscope[] -[discrete] -==== `Model` component +// tag::userstories[] +== User Stories -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[TIP] -Do take a look at <> before attempting to modify the `Model` component. +[width="125%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |New Project Manager|See usage instructions |Refer to instructions when I forget how to use the ProManage -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** +|`* * *` |Project Manager |Add a new person |Maintain a record of that employee -[discrete] -==== `Ui` component +|`* * *` |Project Manager |Delete a person |Remove employees that left the company -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +|`* * *` |Project Manager |Edit a person's details |Change the relevant information -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +|`* * *` |Project Manager |Find a person by name |Locate details of people without having to go through the entire list -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +|`* * *` |Project Manager |Find a person by email |Locate people through their unique work email address -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** +|`* * *` |Project Manager |List the people in alphabetical order by name or by department |View the whole list of +relevant people + +|`* * *` |Project Manager |Add a new event |Maintain a record of that event + +|`* * *` |Project Manager |Delete an event |Remove events that are no longer relevant + +|`* * *` |Project Manager |Edit the information of an event |Update the relevant information + +|`* * *` |Project Manager |View a colleague's events |To monitor my colleague's event schedule before I plan an event + +|`* * *` |Project Manager |View an employee's events on a certain date, month or year |To view the employee's events on a certain date, month or year + +|`* * *` |Project Manager |View a colleague's events on a certain date, month or year |To check if he/she is free for an important meeting + +|`* * *` |Project Manager |Invite an employee to events |Add the person to the a department meeting + +|`* * *` |Project Manager |Invite an employee to events |Add the person to the a company outing + +|`* * *` |Project Manager |Remove people from the event |Remove irrelevant people from an event + +|`* * *` |Project Manager |Remove people from the event |Remove people who I accidentally invited + +|`* * *` |Project Manager |Find an event |Locate details of the event without having to look through the entire list + +|`* * *` |Project Manager |Sort the event list by name |View the events by alphabetical order + +|`* * *` |Project Manager |Sort the event list by time and date |View what are the more recent events of the event list + +|`* * *` |Project Manager |Select an event |To know more information about the event + +|`* * *` |Project Manager |History of commands |To view the commands previously inserted into ProManage + +|`* * *` |Project Manager |Undo command |Undo my previously entered command + +|`* * *` |Project Manager |Redo command |Redo my previously undo command + +|`* * *` |Project Manager |View events related to myself| To avoid looking through the entire event list to find out which events I +need to attend + +|======================================================================= +// end::userstories[] +[width="125%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |User |Exit command | + +|`* * *` |New Employee |See usage instructions |Refer to instructions when I forget how to use the ProManage + +|`* * *` |Employee |Find a person by name |Locate details of people without having to go through the entire list + +|`* * *` |Employee |Find a person by email |Locate people through their unique work email address + +|`* * *` |Employee |Sort the event list by name |View the events by alphabetical order + +|`* * *` |Employee |Sort the event list by time and date |View what are the more recent events of the event list + +|`* * *` |Employee |Undo command |Undo my previously entered command + +|`* * *` |Employee |Redo command |Redo my previously undo command + +|`* * *` |Employee |View a colleague's events |To check out what have my colleague been up to + +|`* * *` |Employee |View events related to myself| To avoid looking through the entire event list to find out which events I +need to attend + +|`* * *` |Employee |Invite my colleagues to events |Add persons to the selected event + + +|======================================================================= + +_{More to be added}_ + +[appendix] + + +== Use Cases + +(For all use cases below, the *System* is the `ProManage` and the *Actor* is the `user`, unless specified otherwise) + +[discrete] +=== Use case: Add person + +*MSS* + +1. User requests to add a person. +2. ProManage adds records down the input information of the person. +3. ProManage adds person. + -image::getting-started-ui-result-before.png[width="200"] +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. + -**After** +Use case ends. + +* 3a. The given index is invalid. + -image::getting-started-ui-result-after.png[width="200"] +[none] +** 3a1. ProManage shows an error message. + -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** +Use case resumes at step 2. -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** +[discrete] +=== Use case: Edit person + +*MSS* + +1. User requests to edit information of person +2. ProManage shows a list of persons +3. User requests to edit a specific person in the list +4. ProManage edits the person + -image::getting-started-ui-status-before.png[width="500"] +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. + -**After** +Use case ends. + +* 3a. The given index is invalid. + -image::getting-started-ui-status-after.png[width="500"] +[none] +** 3a1. ProManage shows an error message. + -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +Use case resumes at step 2. [discrete] -==== `Storage` component +=== Use case: Delete person -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +*MSS* -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +1. User requests to list persons +2. ProManage shows a list of persons +3. User requests to delete a specific person in the list +4. ProManage deletes the person ++ +Use case ends. + +*Extensions* -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +[none] +* 2a. The list is empty. + -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +Use case ends. -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +* 3a. The given index is invalid. ++ +[none] +** 3a1. ProManage shows an error message. ++ +Use case resumes at step 2. -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +// tag::usecases[] +[discrete] +=== Use case: Add Event -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +*MSS* -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +1. User requests to create an event. +2. ProManage displays input format and requests user to enter event input details according to format. +3. User enters event details. +4. Program displays users input and confirm the input with user. +5. User confirms with ProManage +6. ProManage adds event to user's event list. -Examples: ++ +Use case ends. -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +*Extensions* -==== Step-by-step Instructions +[none] +* 3a. User input incorrect format. +[none] +** 3a1. ProManage shows an error message. ++ +Use case resumes at step 2. +// end::usecases[] +[discrete] +=== Use case: Delete event -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +*MSS* -**Main:** +1. User requests to delete an event +2. ProManage shows a list of events +3. User requests to delete a specific event in the list +4. ProManage deletes the event ++ +Use case ends. -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +*Extensions* -**Tests:** +[none] +* 2a. The list is empty. ++ +Use case ends. -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +* 3a. The given index is invalid. ++ +[none] +** 3a1. ProManage shows an error message. ++ +Use case resumes at step 2. -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +[discrete] +=== Use case: Edit event -**Main:** +*MSS* -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +1. User requests to edit an event. +2. ProManage shows a list of events to the user. +3. User requests to edit a specific event in the list +4. User enters the updated details of the event. +5. ProManage confirms edited details with the user. +6. User confirms with ProManage. +7. ProManage edits confirmed details of the chosen event. -**Tests:** ++ +Use case ends. -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +*Extensions* -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +[none] +* 2a. The list is empty. ++ +Use case ends. -**Main:** +* 3a. The given index is invalid. ++ +[none] +** 3a1. ProManage shows an error message. ++ +Use case resumes at step 2. -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. -**Tests:** +[discrete] +=== Use case: Sorting the event list based on the parameter given -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +*MSS* -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +1. User requests to list event in a particular order. +2. ProManage request what order to list the event. +3. User enters the order he/she wishes to list the event. +4. ProManage list the event in that particular order -**Main:** ++ +Use case ends. -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +*Extensions* -**Tests:** +* 3a. Invalid parameters +** 3a1. ProManage shows an error message. +** Use case ends -. Add test for `Remark`, to test the `Remark#equals()` method. +[discrete] +=== Use case: Finding out events which is associated with user -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +*MSS* -**Main:** +1. User login the app +2. User request to list event associated with he/she +3. ProManage list the event that is associated with the user -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) ++ +Use case ends. -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +*Extensions* -**Main:** +* 3a. User did not login +** 3a1. ProManage shows an error message to prompt user to login before using this function +** Use case ends -. Add a new Xml field for `Remark`. +[appendix] -**Tests:** -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +// tag::usecases_invite[] +[discrete] +=== Use case: Invite employees to events -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +*Guarantee*: -**Tests:** +* The invite will not result in clash of events in the employee schedule -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +*MSS* -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +1. User requests to add employee to events. +2. ProManage request employee details and event details. +3. User enters employee and event information. +4. ProManage adds employee to events -**Main:** ++ +Use case ends. -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. -**Tests:** +*Extensions* -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +* 3a. Invalid employee. +** 3a1. ProManage shows an error message. +** Use case ends ++ -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +* 3b. Invalid event. +** 3b1. ProManage shows an error message. +** Use case ends ++ +* 3c. Employee is already invited to event. +** 3c1. AddressBook shows an error message. -**Main:** +** Use case ends ++ +* 3d. Event conflicts with employee's schedule +** 3d1. AddressBook shows an error message. +** Use case ends -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +// end::usecases_invite[] +[discrete] +=== Use case: Remove employees from events -**Tests:** +*Precondition*: User must be of manager or default privilege -. Update `RemarkCommandTest` to test that the `execute()` logic works. +*MSS* -==== Full Solution +1. User requests to remove employee from an event. +2. ProManage request employee details and event details. +3. User enters employee and event information. +4. ProManage remove employee from events -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. ++ +Use case ends. -[appendix] -== Product Scope +*Extensions* -*Target user profile*: +* 3a. Invalid employee. +** 3a1. ProManage shows an error message. +** Use case ends ++ -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* 3b. Invalid event. +** 3b1. ProManage shows an error message. +** Use case ends ++ +* 3c. Event has no attendees +** 3c1. ProManage shows an error message. +** Use case ends ++ +* 3d. Employee is not originally invited to event. +** 3d1. ProManage shows an error message. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +** Use case ends -[appendix] -== User Stories -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +[discrete] +=== Use case: View an employee's schedule on a date/month/year -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +*MSS* -|`* * *` |user |add a new person | +1. User requests to view an employee's schedule. +2. ProManage request employee details and date details. +3. User enters employee and date information. +4. ProManage remove employee from events -|`* * *` |user |delete a person |remove entries that I no longer need ++ +Use case ends. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +*Extensions* -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +* 3a. Invalid employee. +** 3a1. ProManage shows an error message. +** Use case ends ++ +* 3b. Invalid date format +** 3b1. ProManage shows an error message. +** Use case ends -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +// tag::usecases_undo[] +=== Use case: Undo the previously entered command. +*MSS* -_{More to be added}_ +1. User requests to undo previous command +2. ProManage undos the command, restoring the previous state. -[appendix] -== Use Cases ++ +Use case ends. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +*Extensions* -[discrete] -=== Use case: Delete person +* 1a. No commands left to undo. +** 1a1. ProManage shows an error message. +** Use case ends ++ +// end::usecases_undo[] +// tag::usecases_redo[] +=== Use case: Redo the previously undone command. *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 redo the previously undone command +2. ProManage redos the command, restoring the address book or event list state. + + Use case ends. *Extensions* -[none] -* 2a. The list is empty. +* 1a. No previously undone commands available. +** 1a1. ProManage shows an error message. +** Use case ends + -Use case ends. +// end::usecases_undo[] -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. - -_{More to be added}_ - -[appendix] +// tag::nonfunctionalrequirements[] == Non Functional Requirements . Should work on any <> as long as it has Java `9` or higher installed. . Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -_{More to be added}_ +. A user new to the program should be able to navigate and utilize the CLI easily. +. Experienced CLI users should be able to be familiar with all the commands and navigation within the program. +. 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. +. User cannot be in multiple departments. +. The program should respond within 2 seconds after creation, editing, and deletion of events. [appendix] +// end::nonfunctionalrequirements[] == Glossary [[mainstream-os]] Mainstream OS:: @@ -938,11 +1408,35 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ +=== Logging into the app +. Logging without email + +.. Prerequisites: App is not login yet. Privilege is default + +.. Test Case: `login manager` + + Expected: login successfully as manager + +.. Test Case: `login employee` + + Expected: login successfully as manager + +.. Other incorrect login commands to try: `login`,`login ceo` (login without email only accepts manager and employee) + +. Logging with email + +.. Prerequisites: App is not login yet. Privilege is default. Email of an existing person in addressbook + + +.. Test Case: `login as lol@example.com` + + Expected: login successfully as the designation of the person + +.. Other incorrect login commands to try: `login as` `login as invalidemail` + === Deleting a person . Deleting a person while all persons are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all persons using the `list all people` command. Multiple persons in the list. + .. Test case: `delete 1` + Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. .. Test case: `delete 0` + @@ -950,8 +1444,96 @@ _{ more test cases ... }_ .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. +=== Inviting a person to an event + +. Inviting a person while all persons and events are listed + +.. Prerequisites: List all persons using the `list all` command. Multiple persons and events in the list. +.. Test case: `invite 1 to/1` + + Expected: First employee is added to first event. Use `select 1` to verify that the fifth event is present in the event list. +.. Test case: `invite 1 to/1` (continued from test case above) + + Expected: Error message as employee is already invited to event. +.. Test case: `invite 0 to/1` + + Expected: No employee is invited. Error details shown in the status message. +.. Other incorrect invite commands to try: `invite`, `invite x to/1` (where x is larger than the person list size), `invite 1 to/y` (where y is larger than the event list size) + + Expected: Error message. + +=== Removing a person from an event + +. Removing a person while all persons and events are listed + +.. Prerequisites: List all persons using the `list all` command. Multiple persons and events in the list. +.. Test case: `invite 1 to/1` then `remove 1 from/1`+ + Expected: First employee is added to first event and then removed. Use `select 1` to verify that the person is uninvited from the event. +.. Test case: `remove 1 to/1` (continued from test case above) + + Expected: Error message as employee is currently not invited to event. +.. Test case: `delete 0 to/1` + + Expected: No employee is removed. Error details shown in the status message. +.. Other incorrect invite commands to try: `remove`, `remove x to/1` (where x is larger than the person list size), `remove 1 to/y` (where y is larger than the event list size) + + Expected: Similar to error message. + + +=== Listing people and events + +. Listing personnel of specific departments + +.. Prerequisites: Add personnel of different departments into ProManage using the `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 dep/ADMIN des/Manager` +and `add n/Mark p/93455678 e/mark@example.com a/Mark street, block 12, #05-01 dep/Marketing des/Manager` and `add n/Tracy p/98213456 e/tracy@example.com a/Tracy Street, WaterWorks Condo, block J1, #01-01 dep/Finance des/Employee` +.. Test case: `list dep Marketing` + + Expected: The details of Mark or any personnel who is in the Marketing department will appear. Details of the listed personnel will be listed. +.. Test case: `list dep Admin Finance` + + Expected: The details of John and Tracy or any personnel who is in the Admin and Finance department will appear. Details of the listed personnel will be listed. +.. Test case: `list all people` + + Expected: The details of all personnel will be listed. +.. Other incorrect delete commands to try: `list admn`, `list al people` (where the input is not of correct spelling form) + + Expected: Error thrown. + +. Listing all personnel and events + +.. Prerequisites: Add personnel of different departments such as the example above. Add different events into ProManage using the `addEvent n/Board Meeting d/Weekly Meeting l/Conference Room 1 date/2018-09-28 s/12:00 e/23:59` and +`addEvent n/Financial Review d/Review the financial quarter l/Conference Room 4 date/2018-10-23 s/14:00 e/16:30`. + +.. Test case: `list all events` + Expected: The details of all events will be listed. + +.. Test case: `list dep Admin Finance` or any DEPARTMENT -> `list all` + Expected: The details of all personnel and events will be listed. + +=== Selecting an Event + +. Selecting event while all events are listed + +.. Prerequisites: List all persons using the `list all` command. There must be at least one person and one event in the list. + +.. Test case: `invite 1 to/1` then `selectEvent 1` + + Expected: First event in the list is highlighted. Employee list is filtered to only show the first person. + +.. Test case: `selectEvent 2` (continued from test case above) + + Expected: Second event in the list is highlighted. Employee list is empty. + +.. Test case: `selectEvent 0` + + Expected: Error thrown. + +.. Other incorrect selectEvent commands to try: `selectevent` (command is case-sensitive), + `selectEvent a`, `selectEvent x` (where x is larger than the event list shown). + +=== Sorting of events +. Sorting of the event list based on the given params + +.. Prerequisites: No prerequisites + +.. Test case: `sort name` + + Expected: Event list to be sorted according their name. + +.. Test case: `sort date` + + Expected: Event list to be sorted according to the date from the earliest to the latest + +.. Other incorrect sort command to try: `sort` , `sort random` + _{ more test cases ... }_ + + === Saving data . Dealing with missing/corrupted data files diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..c5b18a71f5d8 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += ProManage - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,31 +12,45 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2113-AY1819S1-T16-2/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `T16-2` Since: `Sept 2018` Licence: `MIT` +// tag::introduction[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +ProManage is a *professional desktop application* for companies that specialises in executing projects that undergoes a +long and tedious planning phase. This is particularly useful for companies that have a distinct structure +and chain of command. For each project, there is a project team, which is usually led by a *Manager*. The company would usually +consist of a flexible number of departments such as *Admin, Logistics, Programmes, Finance, Publicity, Marketing and Safety*. +ProManage targets these respective project teams, and allow *Manager* and *Employees* to manage their teams and events. + +Our application aims to ease the mode of task, event allocation and management by providing an all-in-one platform for +the project team. It saves companies' time and money. Our customers will feel at ease communicating with one another in +their project team setting. There is no better way to work than a CLI application that facilitates workflow than ProManage! +More importantly, ProManage is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still +having the benefits of a Graphical User Interface (GUI). ProManage can help you manage project scheduling effectively. +Interested? Jump to the <> to get started. Enjoy! +// end::introduction[] == Quick Start . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `proManage.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your ProManage App. . Double-click the file to start the app. The GUI should appear in a few seconds. + -image::Ui.png[width="790"] -+ . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 dep/ADMIN des/Manager` : adds an employee named + `John Doe` to the Manager List. +* **`addEvent`**`n/Annual General Meeting d/Weekly Meeting l/Conference Room 1 date/2018-09-28 s/12:00 e/23:59 ` : creates +a new event `Annual General Meeting` +* ** `delete`**`3` : deletes the 3rd person shown in the contact list list +* **`delete`**`3` : deletes the 3rd person shown in the contact list list +* *`exit`* : exits the application . Refer to <> for details of each command. @@ -46,118 +60,403 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which + can be used as `add n/Some Meeting`. +* Items in square brackets are optional e.g `DEPT, [MORE_DEPTS,]` can be used as `Dept1, Dept2` or as `Dept1`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[MORE_DEPTS,]...` can be used as + `{nbsp}` (i.e. 0 times), `Dept1`, `Dept1, Dept2` etc. +* Parameters can be in any order e.g. if the command specifies `n/NAME t/TIME`, `t/TIME n/NAME` is also acceptable. +==== + +[NOTE] ==== +Three types priorities: `default`,`manager`,`employee` + +If login command is not executed, the priority will be `default`. + +All clicking features are disabled. Clicking on persons and events will not trigger anything. +==== + === Viewing help : `help` Format: `help` -=== Adding a person: `add` +// tag::login[] +=== Login the app: `login` +Priority level: `default` + +Login to the app as one of the following identity: `manager` or `employee` or `as EMAIL`. + +The user can only login into ProManage if his/her email has been registered in the ProManage system before. + +Once login, you will not be able to login again unless the user chooses to logout. + +Format: login `identity` + +Example: + +`login manager` + +`login as johnd@example.com` +// end::login[] + +// tag::logout[] +=== Logout the app: `logout` +Priority level: `manager`, `employee` + +Logout the app once you have login. + +After successfully logging out, you will be able to login again. + +Format: logout + +Example: + +`logout` +// end::logout[] + +=== Adding an employee : `add` +Priority level: `default`,`manager` + + +Adds an employee to ProManage + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS dep/DEPARTMENT des/DESIGNATION [t/TAG]...` + +[NOTE] +==== -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +* Each employee must have a unique work email address. +* Email address is case insensitive. + +==== [TIP] -A person can have any number of tags (including 0) +An employee can have any number of tags (including 0) -Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +Example: -=== Listing all persons : `list` +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 dep/ADMIN des/Manager` -Shows a list of all persons in the address book. + -Format: `list` -=== Editing a person : `edit` +=== Editing an employee : `edit` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Priority level: `default`,`manager` + +Edits the details of the employee: + +Format: +`edit PERSON_INDEX [n/NAME] [p/PHONE_NUMBER] [a/ADDRESS] [dep/DEPARTMENT] [des/DESIGNATION] [t/TAG]…​` **** -* 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 employee at the specified `INDEX`. The index refers to the index number shown in the displayed event list. The +index *must be a positive integer* 1, 2, 3, ... + * At least one of the optional fields must be provided. +* *Email is not editable* * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags, the existing tags of the employee will be removed i.e adding of tags is not cumulative. +* You can remove all the employee’s tags by typing t/ without specifying any tags after it. **** 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 a/The Vision` + +Edits the phone number and address of the 1st person to be `91234567` and `The Vision` respectively. -=== Locating persons by name: `find` +=== Deleting an employee : `delete` +Priority level: `default`,`manager` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Delete an employee from ProManage: + +Format: `delete PERSON_INDEX` **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Deletes the employee 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, ... **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `list all people` + +`delete 2` + +Deletes the 2nd person in the employee list. + +// tag::list[] +=== Listing all personnel of the company : `list` + +Shows a list of all the personnel, all the events, or those people in the specific department in the project team + +Format/Prompts: + + +Enter command to list all people: `list all people` + +Enter a command to list all the events: `list all events` + +Enter a command to list all people and events: `list all` + +Enter a command to list people in the specific `DEPARTMENT`: `list dep DEPARTMENT` -=== Deleting a person : `delete` +Example: + +`list dep Admin` + +`list dep Admin Finance` +// end::list[] -Deletes the specified person from the address book. + -Format: `delete INDEX` +// tag::addevent[] +=== Adding an event: `addEvent` +Priority level: `default`,`manager` + +Adds an event to ProManage + +Format: `addEvent n/NAME d/DESCRIPTION l/LOCATION date/DATE s/START_TIME e/END_TIME` + **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* DATE needs to be in YYYY-MM-DD format +* START_TIME and END_TIME needs to be in HH:MM format +* START_TIME must be earlier than END_TIME +**** + +[NOTE] +==== +* One location can only hold one event at any time. +* Location is case insensitive +* Events created cannot span multiple days with a single command. +* Leap years are not considered during date validation. +==== + +Example: + +* `addEvent n/Board Meeting d/Weekly Meeting l/Conference Room 1 date/2018-09-28 s/14:00 e/15:45` + +Creates an event named Board Meeting with description "Weekly Meeting" at Conference Room 1 at 2018-09-28, from 14:00 to 15:45. +// end::addevent[] + +// tag::editevent[] +=== Editing an event: `editEvent` +Priority level: `default`,`manager` + +Edits the details of the events: + +Format: `editEvent EVENT_INDEX [n/NAME] [d/DESCRIPTION] [l/LOCATION] [date/DATE] [s/START_TIME] [e/END_TIME]` + +**** +* Edits the event at the specified `EVENT_INDEX`. The index refers to the index number shown in the displayed event list. The +index *must be a positive integer* 1, 2, 3, ... + +* At least one of the optional fields must be provided. +* DATE needs to be in YYYY-MM-DD format +* TIME needs to be in 00:00 format +* START_TIME must be earlier than END_TIME +* Leap years are not considered during date validation. +* Location is case insensitive +* Existing values will be updated to the input values. +**** + +**** +WARNING: One location can only hold one event at any time. + +Event cannot be editted if result clashes with any of the attendees' event schedule +**** + +Examples: + +* `editEvent 10 n/Weekly Meeting d/Check on progress l/Conference Room 2 date/2018-09-10 s/12:00 e/14:00` +// end::editevent[] + +// tag::deleteEvent[] +=== Deleting an event : `deleteEvent` +Priority level: `default`,`manager` + +Delete the specified event from ProManage. + +Format: +`deleteEvent EVENT_INDEX` + +**** +* Deletes the event at the specified `EVENT_INDEX`. +* The index refers to the index number shown in the displayed event list. * The index *must be a positive integer* 1, 2, 3, ... **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `deleteEvent 2` + +Deletes the 2nd event in the address book. +// end::deleteEvent[] + +// tag::sort[] +=== Sorting all events: `sort` +Priority level: all + +Sort the event listing based on the key words provided. + +Able to sort with event's name, event's date, event's starttime, event's endtime. + + +If both event's have the same date then starttime will be compared. + +*Key word:* + +1) event's name: `name` + +2) event's date: `date` + +3) event's starttime: `starttime` + +4) event's endtime: `endtime` + + +Format: sort `key word` + +Example: + +* `sort name` + +Sort the event list alphabetically +// end::sort[] + +// tag::showmine[] +=== Showing events associated with user: `showmine` +Priority level: `manager`, `employee` + +This function is only applicable to users who have login with their email. + +Filter and list out events which they are listed as attendees. -=== Selecting a person : `select` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +Format: `showmine` +// end::showmine[] + +// tag::findEvent[] +=== Searching for the events using keywords: `findEvent` +Priority level: all + +Finds events whose names contain any of the given keywords or whose description contains any of the given keywords (case-insensitive). + +Format: `findEvent KEYWORD [MORE_KEYWORDS]` +Example: + +* `findEvent Meeting Weekly` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* The search is case insensitive +* The order of the keywords does not matter. e.g. `Weekly Meeting` will match `Meeting Weekly` +* Only full words will be matched +* Events matching at least one keyword will be returned +**** +// end::findEvent[] + + +// tag::invite[] +=== Inviting employee to event: `invite` + +Priority level: `all` + +Invites an employee to an event. + +Format: `invite PERSON_INDEX to/EVENT_INDEX` + +Example: + +* `invite 1 to/3` + +Invite 1st employee on employee list to 3rd event on event list + +image::InviteCommandGuide.png[width="480"] + +**** +* Invites the employee at the specified `PERSON_INDEX` *TO* the event at the specified `EVENT_INDEX`. +* The index refers to the index number shown in the displayed employee list and event list respectively. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +**** +WARNING: Employee should not have *already* been invited to the selected event. + +Event invited to *should not clash* with the selected employee's event schedule. +**** + +// end::invite[] + +// tag::remove[] +=== Removing employee from event: `remove` +Priority level: `default`,`manager` + +Removes an employee from the an event. + +Format/Prompts: `remove PERSON_INDEX from/EVENT_INDEX` + +Example: + +* `remove 2 from/4` + +Remove 2nd employee at employee list from 4th event at event list + +image::RemoveCommandGuide.png[width="480"] + +**** +* Remove the employee at the specified `PERSON_INDEX` *FROM* the event at the specified `EVENT_INDEX`. +* The index refers to the index number shown in the displayed person list and event list respectively. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +**** +WARNING: The employee to be remove must be previously invited to an event in order to be removed. +**** +// end::remove[] + +// tag::select[] +=== Selecting a person: `select` +Priority level: `all` + +Selects an employee and view the selected employee's events at the indicated time on the event list panel. + +Format/Prompts: + +Enter a command: `select PERSON_INDEX [date/DATE] [m/MONTH] [y/YEAR]` + +**** +* Select the employee at the specified `PERSON_INDEX` and view all his/her events at certain date/year/month if indicated. +* Events will be displayed on the event list panel on the right in chronological order. +* The index refers to the index number shown in the displayed employee list. * The index *must be a positive integer* `1, 2, 3, ...` +* DATE needs to be in YYYY-MM-DD format +* MONTH needs to be in MM format (e.g. 01, 02, ... , 12) +* YEAR needs to be in YYYY format. (e.g. 2018) +**** + +**** +TIP: MONTH and YEAR can be both used at the same time, resulting in events on the selected MONTH and YEAR to be displayed. +**** + +**** +WARNING: If DATE is indicated, MONTH and YEAR must NOT be indicated. Then, all events of the selected employee at the specific DATE will be displayed. +**** + +image::SelectCommandGuide.png[width="650"] + +Examples: + +* `select 1` : view all events of employee at index 1 +* `select 1 date/2018-10-31` : view all events of employee at index 1 at 2018-10-31 +* `select 1 m/08` : view all events of employee at index 1 in August +* `select 1 y/2018` : view all events of employee at index 1 in 2018 +* `select 1 y/2018 m/08` : view all events of employee at index 1 in August 2018 +// end::select[] + +// tag::selectEvent[] +=== Selecting an event : `selectEvent` +Priority level: all + +Selects the specified event by index and displays all employees attending the event. + +Format: `selectEvent 1` + + +[NOTE] +==== +If any of the attendees are edited or removed, the filtered employee list will not update. The `selectEvent` +command must be input again to view the updated list of attendees. + +==== + +**** +* Selects the event at the specified `EVENT_INDEX`. +* The index refers to the index number shown in the displayed event list. +* The index *must be a positive integer* 1, 2, 3, ... **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `selectEvent 2` + +Selects the 2nd event in the address book and shows its attendees. + +* `invite 1 to/1` (invites an attendee to an event) + +`selectEvent 1` (select the event to show the attendee) + +`edit 1 n/New Name` (edit the name of the attendee) + +`selectEvent 1` (re-selects the event to show the updated name of the attendee) +// end::selectEvent[] + +=== Locating persons by name: `find` +Priority level: all + +Finds employees whose names contain any of the given keywords or whose email matches any of the given keywords (case-insensitive). + +Format: `find KEYWORD [MORE_KEYWORDS]` + +**** +* The search is case insensitive. e.g `hans` will match `Hans`, `hans@example.com` will match `Hans@Example.Com` +* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` +* Only the name and email is searched. +* Only full words will be matched e.g. `Han` will not match `Hans` +* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Only complete email will be matched e.g. `alice` will not match `alice@example.com` +**** + === Listing entered commands : `history` +Priority level: all Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Format/Prompts: + +Enter a command: `history` [NOTE] ==== @@ -166,23 +465,25 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and // tag::undoredo[] === Undoing previous command : `undo` +Priority level: all -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Restores the event schedule or address book to the state before the previous _undoable_ command was executed. Only commands that modify the entries in the event schedule or address book are able to be undone. The unfiltered event schedule and address book will be displayed after undoing the previous command. + +Format/Prompts: + +Enter a command: `undo` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the event schedule's or address book's content (`add`, `delete`, `edit` and `clear`). ==== Examples: * `delete 1` + -`list` + +`list all` + `undo` (reverses the `delete 1` command) + * `select 1` + -`list` + +`list all` + `undo` + The `undo` command fails as there are no undoable commands executed previously. @@ -192,9 +493,11 @@ The `undo` command fails as there are no undoable commands executed previously. `undo` (reverses the `delete 1` command) + === Redoing the previously undone command : `redo` +Priority level: all -Reverses the most recent `undo` command. + -Format: `redo` +Reverses the most recent `undo` command. Only commands that modify the entries in the event schedule or address book will be reversed. The unfiltered event schedule and address book will be displayed after redoing the command. + +Format/Prompts: + +Enter a command: `redo` Examples: @@ -215,46 +518,76 @@ The `redo` command fails as there are no `undo` commands executed previously. // end::undoredo[] === Clearing all entries : `clear` +Priority level: all -Clears all entries from the address book. + -Format: `clear` +Clears all entries from the event schedule. + +Format/Prompts: + +Enter a command: `clear` === Exiting the program : `exit` +Priority level: all Exits the program. + -Format: `exit` +Format/Prompts: + +Enter a command: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +ProManage data saves data in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` - -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] == FAQ *Q*: How do I transfer my data to another Computer? + *A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +// tag::commandSummary[] == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` +* *Help*: `help` + +* *Login*: `login IDENTITY` where IDENTITY is either manager or employee or `login as existingemail@example.com` + +* *Logout*: `logout` + +* *Add Employee*: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS dep/DEPARTMENT des/DESIGNATION [t/TAG]...` + +* *Edit Employee* : `edit PERSON_INDEX [p/PHONE_NUMBER] [a/ADDRESS] [dep/DEPARTMENT] [des/DESIGNATION] [t/TAG]…​` + +* *Delete Employee* : `delete PERSON_INDEX` + +* *Find Employee* : `find KEYWORD [MORE_KEYWORDS]` + +* *List* : `list all` OR `list all people` OR `list all events` or `list dep DEPARTMENT` + +* *Sort* : `sort KEYWORD` + +* *Add Event* : `addEvent n/NAME d/DESCRIPTION l/LOCATION date/DATE s/START_TIME e/END_TIME` + +* *Edit Event* : `editEvent EVENT_INDEX [n/NAME] [d/DESCRIPTION] [l/LOCATION] [date/DATE] [s/START_TIME] [e/END_TIME]` + +* *Delete Event* : `deleteEvent EVENT_INDEX` + +* *Find Event* : `findEvent KEYWORD [MORE KEYWORDS]` + +* *Invite* : `invite PERSON_INDEX to/EVENT_INDEX` + +* *Remove* : `remove PERSON_INDEX from/EVENT_INDEX` + +* *Select Employee* : `select PERSON_INDEX [date/DATE] [m/MONTH] [y/YEAR]` + +* *Select Event* : `selectEvent EVENT_INDEX` + +* *Show Mine*: `showmine` + * *History* : `history` + * *Undo* : `undo` + * *Redo* : `redo` + +* *Clear* : `clear` + +* *Exit* : `exit` +// end::commandSummary[] diff --git a/docs/diagrams/EventClashDiagram.pptx b/docs/diagrams/EventClashDiagram.pptx new file mode 100644 index 000000000000..21852fcd9c81 Binary files /dev/null and b/docs/diagrams/EventClashDiagram.pptx differ diff --git a/docs/diagrams/InviteSequenceDiagram.pptx b/docs/diagrams/InviteSequenceDiagram.pptx new file mode 100644 index 000000000000..d2d90fc5a276 Binary files /dev/null and b/docs/diagrams/InviteSequenceDiagram.pptx differ diff --git a/docs/diagrams/InviteSequenceDiagramNew.pptx b/docs/diagrams/InviteSequenceDiagramNew.pptx new file mode 100644 index 000000000000..656e1c37bf87 Binary files /dev/null and b/docs/diagrams/InviteSequenceDiagramNew.pptx differ diff --git a/docs/diagrams/ListSequenceDiagram.pptx b/docs/diagrams/ListSequenceDiagram.pptx new file mode 100644 index 000000000000..67d0651e4e58 Binary files /dev/null and b/docs/diagrams/ListSequenceDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..eb1659bef32d 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx index d0561dfd305a..2cfeea8591e9 100644 Binary files a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx and b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..59a5829da464 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ParserClassDiagram.pptx b/docs/diagrams/ParserClassDiagram.pptx new file mode 100644 index 000000000000..d59842a8125c Binary files /dev/null and b/docs/diagrams/ParserClassDiagram.pptx differ diff --git a/docs/diagrams/SelectCommandSequenceDiagram.pptx b/docs/diagrams/SelectCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..c8ca037a1cef Binary files /dev/null and b/docs/diagrams/SelectCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/SortNewCommand1StateListDiagram.pptx b/docs/diagrams/SortNewCommand1StateListDiagram.pptx new file mode 100644 index 000000000000..eeece79e3c66 Binary files /dev/null and b/docs/diagrams/SortNewCommand1StateListDiagram.pptx differ diff --git a/docs/diagrams/SortSequenceDiagram.pptx b/docs/diagrams/SortSequenceDiagram.pptx new file mode 100644 index 000000000000..fc1daf67a647 Binary files /dev/null and b/docs/diagrams/SortSequenceDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..175936bd59f3 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..2983cc113ef3 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/images/EventAndAttendeeClassDiagram.png b/docs/images/EventAndAttendeeClassDiagram.png new file mode 100644 index 000000000000..8dcfcfd2b322 Binary files /dev/null and b/docs/images/EventAndAttendeeClassDiagram.png differ diff --git a/docs/images/EventClashDiagram.png b/docs/images/EventClashDiagram.png new file mode 100644 index 000000000000..304ad8cdcb0b Binary files /dev/null and b/docs/images/EventClashDiagram.png differ diff --git a/docs/images/EventClassDiagram.png b/docs/images/EventClassDiagram.png new file mode 100644 index 000000000000..7b6800e91cd3 Binary files /dev/null and b/docs/images/EventClassDiagram.png differ diff --git a/docs/images/InviteCommandGuide.png b/docs/images/InviteCommandGuide.png new file mode 100644 index 000000000000..67dac1ae14dc Binary files /dev/null and b/docs/images/InviteCommandGuide.png differ diff --git a/docs/images/InviteSequenceDiagram.png b/docs/images/InviteSequenceDiagram.png new file mode 100644 index 000000000000..8306885f236a Binary files /dev/null and b/docs/images/InviteSequenceDiagram.png differ diff --git a/docs/images/ListSequenceDiagram.png b/docs/images/ListSequenceDiagram.png new file mode 100644 index 000000000000..64d7fe8529ad Binary files /dev/null and b/docs/images/ListSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..8387de1ad598 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassBetterOopDiagram.png b/docs/images/ModelClassBetterOopDiagram.png index 9ba8eb5e31d0..422432f6bc08 100644 Binary files a/docs/images/ModelClassBetterOopDiagram.png and b/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..348df3671eb4 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClassDiagram.png b/docs/images/ParserClassDiagram.png new file mode 100644 index 000000000000..fa7a50c20653 Binary files /dev/null and b/docs/images/ParserClassDiagram.png differ diff --git a/docs/images/RemoveCommandGuide.png b/docs/images/RemoveCommandGuide.png new file mode 100644 index 000000000000..6e0189bb25cb Binary files /dev/null and b/docs/images/RemoveCommandGuide.png differ diff --git a/docs/images/SelectCommandGuide.png b/docs/images/SelectCommandGuide.png new file mode 100644 index 000000000000..9a0260db9d0a Binary files /dev/null and b/docs/images/SelectCommandGuide.png differ diff --git a/docs/images/SelectSequenceDiagram.png b/docs/images/SelectSequenceDiagram.png new file mode 100644 index 000000000000..10a0a913b944 Binary files /dev/null and b/docs/images/SelectSequenceDiagram.png differ diff --git a/docs/images/SortNewCommand1StateListDiagram.png b/docs/images/SortNewCommand1StateListDiagram.png new file mode 100644 index 000000000000..a668045e25a6 Binary files /dev/null and b/docs/images/SortNewCommand1StateListDiagram.png differ diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png new file mode 100644 index 000000000000..14e32cdcaaf2 Binary files /dev/null and b/docs/images/SortSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..1d6b21c451d1 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui-mock.png b/docs/images/Ui-mock.png new file mode 100644 index 000000000000..3f24418b531b Binary files /dev/null and b/docs/images/Ui-mock.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..4bf16160a8d7 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 369469ef176e..009d2cf0e1e6 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoModelInitializationStateListDiagram.png b/docs/images/UndoRedoModelInitializationStateListDiagram.png new file mode 100644 index 000000000000..1d47b1997ea9 Binary files /dev/null and b/docs/images/UndoRedoModelInitializationStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommandAB1StateListDiagram.png b/docs/images/UndoRedoNewCommandAB1StateListDiagram.png new file mode 100644 index 000000000000..e38211c3832d Binary files /dev/null and b/docs/images/UndoRedoNewCommandAB1StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommandAB2StateListDiagram.png b/docs/images/UndoRedoNewCommandAB2StateListDiagram.png new file mode 100644 index 000000000000..48de834f30db Binary files /dev/null and b/docs/images/UndoRedoNewCommandAB2StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommandAB3StateListDiagram.png b/docs/images/UndoRedoNewCommandAB3StateListDiagram.png new file mode 100644 index 000000000000..8bb3175a23d9 Binary files /dev/null and b/docs/images/UndoRedoNewCommandAB3StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommandBothStateListDiagram.png b/docs/images/UndoRedoNewCommandBothStateListDiagram.png new file mode 100644 index 000000000000..e46a5e27fef0 Binary files /dev/null and b/docs/images/UndoRedoNewCommandBothStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommandELStateListDiagram.png b/docs/images/UndoRedoNewCommandELStateListDiagram.png new file mode 100644 index 000000000000..93e43566a648 Binary files /dev/null and b/docs/images/UndoRedoNewCommandELStateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..b5a573af648e 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/fr3ddy4.png b/docs/images/fr3ddy4.png new file mode 100644 index 000000000000..9275116cd175 Binary files /dev/null and b/docs/images/fr3ddy4.png differ diff --git a/docs/images/icedcoffeeboy.png b/docs/images/icedcoffeeboy.png new file mode 100644 index 000000000000..214b72a6822b Binary files /dev/null and b/docs/images/icedcoffeeboy.png differ diff --git a/docs/images/jeevz10.png b/docs/images/jeevz10.png new file mode 100644 index 000000000000..2004a748c707 Binary files /dev/null and b/docs/images/jeevz10.png differ diff --git a/docs/images/jieliangang.png b/docs/images/jieliangang.png new file mode 100644 index 000000000000..b2820acc4e58 Binary files /dev/null and b/docs/images/jieliangang.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/perrythewang.png b/docs/images/perrythewang.png new file mode 100644 index 000000000000..c77401e8b2aa Binary files /dev/null and b/docs/images/perrythewang.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/IcedCoffeeBoy.adoc b/docs/team/IcedCoffeeBoy.adoc new file mode 100644 index 000000000000..f83d7012c2aa --- /dev/null +++ b/docs/team/IcedCoffeeBoy.adoc @@ -0,0 +1,96 @@ += Lee Ming Liang - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProManage + +ProManage is a *professional desktop application* for companies that specialises in executing projects that undergoes a +long and tedious planning phase. ProManage allow *Manager* and *Employees* of project teams to manage their team members and events easily. +ProManage is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + + +== Overview + +This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2. + +== Summary of contributions + +* *Major enhancement*: Added ability to *login, logout, show user personalised event list* via the `login`, `logout` and `showmine` command +** What it does: Allow users to login according to designation +** Justification: These features allow users to use application's function according to their designation. Users who login + with their personal email are able to list event they are listed as an attendee, therefore remove the need to go through + the list of events to find the events they are associated with. +** Highlights: Users who are at lower privilege level are not allow to use certain commands like add event, edit event, etc. +The app will give invalid privilege error message when users attempts to use inappropriate function that does not match their privilege level. +This is useful in helping users to differentiate between incorrect input commands and insufficient privilege commands. + +* *Minor enhancement*: +** Created `sort` command which allows user to sort the event list according to the parameters they entered. +This create convenient for users to find event in the event list. For example, they are able to sort events according +to their date and find events that are occurring at earlier dates. +** Created the storage components for events so that the event created could be save as XML format. +** Created the event class and it relevant components to transform the addressbook app to an event planner app. +** Updated the mainapp to load eventlist xml or a sample of events when the app launches. +** Implemented ProManageParser which parser all commands + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=IcedCoffeeBoy[Functional code]] + +* *Other contributions*: + +** Project management: +*** Helped to Managed releases on GitHub +*** Managed the issue tracker on GitHub by creating new issues and closed existing issues that already resolved. +*** Wrote test cases to increase coverage + [https://github.com/CS2113-AY1819S1-T16-2/main/pull/36[PR1]] + [https://github.com/CS2113-AY1819S1-T16-2/main/pull/56[PR2]] + [https://github.com/CS2113-AY1819S1-T16-2/main/pull/116[PR3]] + [https://github.com/CS2113-AY1819S1-T16-2/main/pull/144[PR4]] + [https://github.com/CS2113-AY1819S1-T16-2/main/pull/138[PR5]] + +** Documentation: +*** Updated the User Guide for Practical Exam 1 +*** Updated the Developer Guide to include `sort` command, user stories for sort, login and use case for showmine, +class diagram for parser. +*** Include new test case for `login` and `sort` command for Appendix F under developer guide. + + +** Community: +*** PRs reviewed and merged +*** Helped other teammates to debug their code + +** Tools: +*** Setting up of reponsense + + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=login] + +include::../UserGuide.adoc[tag=logout] + +include::../UserGuide.adoc[tag=sort] + +include::../UserGuide.adoc[tag=showmine] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=Parser] + +include::../DeveloperGuide.adoc[tag=Sort] + + + + + + diff --git a/docs/team/fr3ddy4.adoc b/docs/team/fr3ddy4.adoc new file mode 100644 index 000000000000..58173c88c998 --- /dev/null +++ b/docs/team/fr3ddy4.adoc @@ -0,0 +1,48 @@ += Ruslan Farkhutdinov - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProManage + +--- + +== Overview + +This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2. + +== Summary of contributions + +* *Major enhancement*: added *the ability to find events using keywords events* via the `findEvent` command +** What it does: allows the user to search for an event using some keywords. +** Justification: This feature is important as it allows users to access the required events quickly if they know its name or some parts of description. + + +* *Minor enhancement*: +** Created the test cases for classes of findEvent command +** Updated the documentation +** Created the event sorting function + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=fr3ddy4[Functional code]] + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=findEvent] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=FindEvent] + + + diff --git a/docs/team/jeevz10.adoc b/docs/team/jeevz10.adoc new file mode 100644 index 000000000000..02ee46fe7542 --- /dev/null +++ b/docs/team/jeevz10.adoc @@ -0,0 +1,80 @@ += Jeevan Neralakere Somashekhar - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProManage + +--- + +== Overview + +This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2. + +== Summary of contributions + +* *Major enhancement*: added *`department` attribute for each person added into ProManage*. In addition, added *the ability to `list` all people and events, with the option to view people based on their departments* via the `list` command. +** What it does: allows the user to `list people` and events. In addition, the user has the option to `list` people based on their departments. +** Justification: This feature facilitates personal information collection for users. Users can easily view all the personal information of the relevant people he/she is looking for. Similarly, the user can view all information of all events. +** Highlights: This feature considers which `department` the person is in. The user can also view the personal information of people from multiple departments. + + +* *Minor enhancement*: +** added a `designation` attribute to each person added into ProManage. Essentially, for v1.4, each person is either a manager or an employee. +** improved the `login` command, allowing existing users of ProManage to login with their email. This helps to increases user friendliness. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=jeevz10&sort=displayName&since=2018-09-12&until=2018-11-03&timeframe=day&reverse=false&repoSort=true] + +* *Other contributions*: + +** Project management: +*** Helped to Managed releases on GitHub +*** Help raised issues project dashboard on GitHub https://github.com/CS2113-AY1819S1-T16-2/main/issues/28 https://github.com/CS2113-AY1819S1-T16-2/main/issues/30 https://github.com/CS2113-AY1819S1-T16-2/main/issues/44 https://github.com/CS2113-AY1819S1-T16-2/main/issues/136 +*** Wrote additional tests to increase coverage from 82% to 84% https://github.com/CS2113-AY1819S1-T16-2/main/pull/114 +** Documentation: +*** Updated the User Guide to include `list` command and updated the `add` , `edit` and `login` commands +*** Contributed to the updates on the User Guide: https://github.com/CS2113-AY1819S1-T16-2/main/pull/25 https://github.com/CS2113-AY1819S1-T16-2/main/pull/57 https://github.com/CS2113-AY1819S1-T16-2/main/pull/82 https://github.com/CS2113-AY1819S1-T16-2/main/pull/155 +*** Updated the Developer Guide to include `list`, introduction, project scope, uses stories, use cases and non-functional requirements and `list` feature: https://github.com/CS2113-AY1819S1-T16-2/main/pull/6 https://github.com/CS2113-AY1819S1-T16-2/main/pull/57 https://github.com/CS2113-AY1819S1-T16-2/main/pull/82 https://github.com/CS2113-AY1819S1-T16-2/main/pull/155 +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2113-AY1819S1-T16-2/main/pull/134 + + + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=introduction] + +include::../UserGuide.adoc[tag=login] + +include::../UserGuide.adoc[tag=list] + +include::../UserGuide.adoc[tag=commandSummary] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=List] + +include::../DeveloperGuide.adoc[tag=productscope] + +include::../DeveloperGuide.adoc[tag=userstories] + +include::../DeveloperGuide.adoc[tag=usecases] + +include::../DeveloperGuide.adoc[tag=nonfunctionalrequirements] + + + + + + diff --git a/docs/team/jieliangang.adoc b/docs/team/jieliangang.adoc new file mode 100644 index 000000000000..65892f1ada0c --- /dev/null +++ b/docs/team/jieliangang.adoc @@ -0,0 +1,81 @@ += Ang Jie Liang - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProManage +--- +== Overview + +ProManage is a *professional desktop application* for companies that specialises in handling and executing projects. +ProManage allow *Manager* and *Employees* of project teams to manage their team members and events easily. +ProManage is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + +This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2. + +== Summary of contributions + +* *Major enhancement*: added *the ability to manage and view employees' events* via the `invite`, `remove` and `select` commands +** What it does: allows the user to invite employees to events, remove employees from events, and select and view an employee's event schedule on the indicated date, month or year which is displayed on the *event list panel*. +** Justification: This feature facilitates managing and scheduling of events among teams of employees who have different work schedules, and allow managers and employees to monitor and handle their event schedules easily. Schedule conflicts can be avoided and resolved easily with this enhancement. +** Highlights: This feature considers whether the event an employee is invited to *clashes* with the particular employee's work schedule, and *guarantees* that schedule conflict will not occur. The underlying `Event` model structure implemented for this enhancement is carefully designed and allows extension commands `viewmine` and `selectEvent` to be easily implemented. + + +* *Minor enhancement*: +** set up the event list panel to display the events for the UI +** added the `addEvent` command, which also warns the user when the planned event clashes or coincides with other existing events at the same location and time +** improved the `editEvent` command, which warns the user when the changed time or date of the edited event leads to schedule conflicts with any of the event's attendees + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=jieliangang[Functional and Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.1` - `v1.4` (6 releases) on GitHub +*** Managed the issue tracker and project dashboard on GitHub +** Enhancements to existing features: +*** Updated the GUI color scheme https://github.com/CS2113-AY1819S1-T16-2/main/pull/134[#134] +*** Updated the `add` and `edit` command to distinguish employees by their unique email address +*** Updated the `find` command to also search for employees via their email address +*** Wrote additional tests for existing features to increase coverage from 82% to 87% https://github.com/CS2113-AY1819S1-T16-2/main/pull/128[#128] https://github.com/CS2113-AY1819S1-T16-2/main/pull/130[#130] https://github.com/CS2113-AY1819S1-T16-2/main/pull/131[#131] +** Documentation: +*** Updated the User Guide to include `addEvent`, `editEvent`, `invite`, `remove` and `select` command +*** Updated the Developer Guide to include implementation details, use case, user stories and instructions for manual testing for `invite` and `select` command. +*** Updated the `Model`, `UI` and `Logic` class diagram in Developer Guide +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2113-AY1819S1-T16-2/main/pull/77[#77] https://github.com/CS2113-AY1819S1-T16-2/main/pull/114[#114] https://github.com/CS2113-AY1819S1-T16-2/main/pull/122[#122] +*** Reported feature bugs discovered during system testing (Issue: https://github.com/CS2113-AY1819S1-T16-2/main/issues/75[#75] https://github.com/CS2113-AY1819S1-T16-2/main/issues/125[#125] https://github.com/CS2113-AY1819S1-T16-2/main/issues/126[#126]) +*** Prepare sample data for final demo https://github.com/CS2113-AY1819S1-T16-2/main/pull/149[#149] +** Tools: +*** Integrated GitHub Plugins (Coveralls, Travis CI, AppVeyor) +*** Automated team documentation build on GitHub pages + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=invite] + +include::../UserGuide.adoc[tag=remove] + +include::../UserGuide.adoc[tag=select] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=Storage] + +include::../DeveloperGuide.adoc[tag=Model] + +include::../DeveloperGuide.adoc[tag=Invite] + + + diff --git a/docs/team/perrythewang.adoc b/docs/team/perrythewang.adoc new file mode 100644 index 000000000000..b227bfb1aa44 --- /dev/null +++ b/docs/team/perrythewang.adoc @@ -0,0 +1,87 @@ += Perry Wang - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProManage + +ProManage is a *professional desktop application* for companies that specialises in executing projects that undergoes a +long and tedious planning phase. ProManage allow *Manager* and *Employees* of project teams to manage their team members and events easily. +ProManage is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). + +== Overview + +This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2. + +== Summary of contributions + +* *Major enhancement*: Extended *undo and redo capability of the model* via the `undo` and `redo` commands to all existing or new commands. +** Justification: With so many commands with different purposes, it can be very easy to make mistakes with unintended +effects. For example, if an employee was wrongly renamed, without the undo command, the employee's original name +would be irreversibly lost. Being able to undo or redo commands improves the flexibility of the app, where +users do not have to be worried that their wrongly entered commands have irreversible effects. +** Highlights: This implementation required an overhaul of the existing `Model` to enable intelligent undo and redo +capability to both the address book and event list, where the model recognises which of them to undo or redo. +As they are intertwined with all other modifying commands, extensive testing was required to harden the app +against bugs and ensure that the commands worked properly in all scenarios. + + +* *Minor enhancement*: +** Created EditEvent command which allows the user to edit an event in the event list according to the parameters +they entered. Regex was used to ensure that the input parameters were valid. +** Created DeleteEvent command which allows the user to delete an event specified by its index on the filtered +event list. +** Created SelectEvent command which allows the user to select an event to show its attendees. It filters the +person list on the UI to show only the attendees. +** Implemented `VersionedEventList` class as a subclass of `EventList` but with historical record. +** Implemented `EventModel` interface and `PersonModel` interface which are extended by the `Model` interface. + + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=perrythewang[Functional code]] + +* *Other contributions*: + +** Project management: +*** Helped to manage releases on GitHub +*** Managed the issue tracker on GitHub by creating new issues and closed existing issues that have been resolved. +*** Wrote test cases to increase coverage by 1.2% in total + + PR 1: https://github.com/CS2113-AY1819S1-T16-2/main/pull/147 + + PR 2: https://github.com/CS2113-AY1819S1-T16-2/main/pull/148 + + PR 3: https://github.com/CS2113-AY1819S1-T16-2/main/pull/150 + + + +** Documentation: +*** Updated the User Guide for `deleteEvent`, `selectEvent`, `undo`, and `redo` commands. +*** Updated the Developer Guide to include implementation and usage example of `undo` and `redo` commands. + +** Community: +*** PRs reviewed and merged +*** Helped other teammates to debug their code + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=deleteEvent] + +include::../UserGuide.adoc[tag=selectEvent] + +include::../UserGuide.adoc[tag=undoredo] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=UndoRedo] + + + + + diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..f84351348ad7 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -21,26 +21,33 @@ import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; import seedu.address.model.AddressBook; +import seedu.address.model.EventList; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEventList; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; +import seedu.address.model.util.SampleEventUtil; import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.EventStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlEventStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; + + /** * The main entry point to the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 3, 2, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +61,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing ProManage ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -63,7 +70,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + EventStorage eventStorage = new XmlEventStorage(userPrefs.getEventlistPath()); + storage = new StorageManager(addressBookStorage, eventStorage, userPrefsStorage); initLogging(config); @@ -74,8 +82,10 @@ public void init() throws Exception { ui = new UiManager(logic, config, userPrefs); initEventsCenter(); + } + /** * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
* The data from the sample address book will be used instead if {@code storage}'s address book is not found, @@ -83,22 +93,41 @@ public void init() throws Exception { */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional eventListOptional; + ReadOnlyAddressBook initialAddressData; + ReadOnlyEventList initialEventListData; try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { logger.info("Data file not found. Will be starting with a sample AddressBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialAddressData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + initialAddressData = new AddressBook(); } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + initialAddressData = new AddressBook(); } - - return new ModelManager(initialData, userPrefs); + //@@author: IcedCoffeeBoy + // Done by IcedCoffeeBoy + try { + eventListOptional = storage.readEventList(); + if (!eventListOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample EventList"); + } + initialEventListData = eventListOptional.orElseGet(SampleEventUtil::getSampleEventList); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty EventList"); + initialEventListData = new EventList(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty EventList"); + initialEventListData = new EventList(); + } + initialEventListData.sortByName(); + return new ModelManager(initialAddressData, initialEventListData, userPrefs); + //End of modification + //@@author: } private void initLogging(Config config) { @@ -179,13 +208,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting ProManage " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping ProManage ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/address/commons/core/ComponentManager.java index 05a400773ae8..165311fe21c2 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/address/commons/core/ComponentManager.java @@ -4,7 +4,7 @@ /** * Base class for *Manager classes - * + *

* Registers the class' event handlers in eventsCenter */ public abstract class ComponentManager { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..eed16b5c65cc 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "ProManage"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 5316a1d87d3e..15bef81354a6 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -14,8 +14,8 @@ * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log + * file reaches 5MB big, up to a maximum of 5 files.
*/ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; @@ -97,6 +97,7 @@ private static void addFileHandler(Logger logger) { /** * Creates a {@code FileHandler} for the log file. + * * @throws IOException if there are problems opening the file. */ private static FileHandler createFileHandler() throws IOException { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..08e01b79c862 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -8,6 +8,8 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_EVENTS_LISTED_OVERVIEW = "%1$d events listed!"; } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index e117f91b3b2e..169c35d6539a 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -47,6 +47,7 @@ public boolean isEarlyAccess() { /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c099..b1285443c15b 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -2,7 +2,7 @@ /** * Represents a zero-based or one-based index. - * + *

* {@code Index} should be used right from the start (when parsing in a new user input), so that if the current * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what * base the other component is using for its index. However, after receiving the {@code Index}, that component can diff --git a/src/main/java/seedu/address/commons/events/model/EventListChangedEvent.java b/src/main/java/seedu/address/commons/events/model/EventListChangedEvent.java new file mode 100644 index 000000000000..3acf8f812716 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/EventListChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyEventList; + +/** Indicates the EventList in the model has changed*/ +public class EventListChangedEvent extends BaseEvent { + + public final ReadOnlyEventList data; + + public EventListChangedEvent(ReadOnlyEventList data) { + this.data = data; + } + + @Override + public String toString() { + return "number of events " + data.getEventList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/EventPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/EventPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..06c12d802e32 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/EventPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.event.Event; + +/** + * Represents a selection change in the Event List Panel + */ +public class EventPanelSelectionChangedEvent extends BaseEvent { + + + private final Event newSelection; + + public EventPanelSelectionChangedEvent(Event newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Event getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToEventListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToEventListRequestEvent.java new file mode 100644 index 000000000000..ee3683e7ab21 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/JumpToEventListRequestEvent.java @@ -0,0 +1,22 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of events + */ +public class JumpToEventListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToEventListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd92..1441a80c6711 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -1,6 +1,7 @@ package seedu.address.commons.util; import java.io.IOException; +import java.net.URI; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -18,8 +19,9 @@ public static boolean isFileExists(Path file) { } /** - * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, + * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(URI)} }, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -33,6 +35,7 @@ public static boolean isValidPath(String path) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..66f512c191f9 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,6 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -22,6 +23,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of events */ + ObservableList getFilteredEventList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..b661c2bacb87 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -8,9 +8,10 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.ProManageParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -21,19 +22,20 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private ProManageParser proManageParser; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + this.proManageParser = new ProManageParser(); } + @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = proManageParser.parseCommand(commandText, model); return command.execute(model, history); } finally { history.add(commandText); @@ -45,6 +47,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredEventList() { + return model.getFilteredEventList(); + } + @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index d88e831ff1ce..7e2de44ba268 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,10 +1,14 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; + import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESIGNATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.logic.CommandHistory; @@ -19,23 +23,28 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an employee to the company address book. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_DEPARTMENT + "DEPARTMENT " + + PREFIX_DESIGNATION + "DESIGNATION " + "[" + 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_DEPARTMENT + "Admin " + + PREFIX_DESIGNATION + "Manager " + 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_SUCCESS = "New employee added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = + "An employee with the email address is already registered"; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/AddEventCommand.java b/src/main/java/seedu/address/logic/commands/AddEventCommand.java new file mode 100644 index 000000000000..b16756a22806 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddEventCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +//@@author jieliangang +/** + * Creates an event in event list. + */ +public class AddEventCommand extends Command { + + public static final String COMMAND_WORD = "addEvent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates an event to the event list. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_LOCATION + "LOCATION " + + PREFIX_DATE + "DATE " + + PREFIX_START_DATE + "START DATE " + + PREFIX_END_DATE + "END DATE \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Board Meeting " + + PREFIX_DESCRIPTION + "Weekly Meeting " + + PREFIX_LOCATION + "Conference Room 1 " + + PREFIX_DATE + "2018-09-28 " + + PREFIX_START_DATE + "12:00 " + + PREFIX_END_DATE + "23:59 "; + + public static final String MESSAGE_SUCCESS = "New event added: %1$s"; + public static final String MESSAGE_DUPLICATE_EVENT = + "The location has been booked for another event at the selected time"; + + private final Event toAdd; + + /** + * Creates an AddEventCommand to add the specified {@code event} + */ + public AddEventCommand(Event event) { + requireNonNull(event); + toAdd = event; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasEvent(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); + } + + model.addEvent(toAdd); + model.commitEventList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddEventCommand // instanceof handles nulls + && toAdd.equals(((AddEventCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..553f425e957f 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -4,6 +4,7 @@ import seedu.address.logic.CommandHistory; import seedu.address.model.AddressBook; +import seedu.address.model.EventList; import seedu.address.model.Model; /** @@ -18,8 +19,9 @@ public class ClearCommand extends Command { @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.resetData(new AddressBook()); + model.resetData(new AddressBook(), new EventList()); model.commitAddressBook(); + model.commitEventList(); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index a20e9d49eac7..907e50f1badc 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -42,7 +42,9 @@ public CommandResult execute(Model model, CommandHistory history) throws Command Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); + model.removePersonFromAllEvents(personToDelete); model.commitAddressBook(); + model.commitEventList(); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java b/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java new file mode 100644 index 000000000000..b2c803c5b6bf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +/** + * Cancels an event identified using it's displayed index from the address book. + */ +public class DeleteEventCommand extends Command { + + public static final String COMMAND_WORD = "deleteEvent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the event identified by the index number used in the displayed event list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_CANCEL_EVENT_SUCCESS = "Cancelled Event: %1$s"; + + private final Index targetIndex; + + public DeleteEventCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredEventList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteEvent(eventToDelete); + model.commitEventList(); + return new CommandResult(String.format(MESSAGE_CANCEL_EVENT_SUCCESS, eventToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteEventCommand // instanceof handles nulls + && targetIndex.equals(((DeleteEventCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index dc782d8e230f..59cae7641782 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -2,7 +2,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESIGNATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -21,6 +22,8 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Address; +import seedu.address.model.person.Department; +import seedu.address.model.person.Designation; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; @@ -34,22 +37,23 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the employee identified " + + "by the index number used in the displayed employee list. " + + "Existing values will be overwritten by the input values. Email is NOT editable\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_DEPARTMENT + "DEPARTMENT] " + + "[" + PREFIX_DESIGNATION + "DESIGNATION] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_ADDRESS + "The Vision"; 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_DUPLICATE_PERSON = "This employee already exists in the address book."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -58,6 +62,7 @@ public class EditCommand extends Command { * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ + public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); @@ -97,11 +102,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); + Email email = personToEdit.getEmail(); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Department updatedDepartment = editPersonDescriptor.getDepartment().orElse(personToEdit.getDepartment()); + Designation updatedDesignation = editPersonDescriptor.getDesignation().orElse(personToEdit.getDesignation()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, email, updatedAddress, updatedDepartment, + updatedDesignation, updatedTags); } @Override @@ -129,10 +137,12 @@ public boolean equals(Object other) { public static class EditPersonDescriptor { private Name name; private Phone phone; - private Email email; private Address address; + private Department department; + private Designation designation; private Set tags; + public EditPersonDescriptor() {} /** @@ -142,16 +152,18 @@ public EditPersonDescriptor() {} public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); - setEmail(toCopy.email); setAddress(toCopy.address); + setDepartment(toCopy.department); + setDesignation(toCopy.designation); setTags(toCopy.tags); + } /** * 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, address, department, designation, tags); } public void setName(Name name) { @@ -170,14 +182,6 @@ public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - public void setAddress(Address address) { this.address = address; } @@ -186,6 +190,22 @@ public Optional

getAddress() { return Optional.ofNullable(address); } + public void setDepartment(Department department) { + this.department = department; + } + + public Optional getDepartment() { + return Optional.ofNullable(department); + } + + public void setDesignation(Designation designation) { + this.designation = designation; + } + + public Optional getDesignation() { + return Optional.ofNullable(designation); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -218,10 +238,11 @@ public boolean equals(Object other) { // state check EditPersonDescriptor e = (EditPersonDescriptor) other; - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) + return getPhone().equals(e.getPhone()) + && getName().equals(e.getName()) && getAddress().equals(e.getAddress()) + && getDepartment().equals(e.getDepartment()) + && getDesignation().equals(e.getDesignation()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/EditEventCommand.java b/src/main/java/seedu/address/logic/commands/EditEventCommand.java new file mode 100644 index 000000000000..9d3e02f7d256 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditEventCommand.java @@ -0,0 +1,249 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Attendees; +import seedu.address.model.event.Description; +import seedu.address.model.event.EndTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Location; +import seedu.address.model.event.StartTime; + +/** + * Edits the details of an existing event in the address book. + */ +public class EditEventCommand extends Command { + + public static final String COMMAND_WORD = "editEvent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the event identified " + + "by the index number used in the displayed event list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_START_DATE + "START_TIME] " + + "[" + PREFIX_END_DATE + "END_TIME] " + + "[" + PREFIX_LOCATION + "LOCATION]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NAME + "Strategy Meeting " + + PREFIX_LOCATION + "Meeting Room 2 "; + + public static final String MESSAGE_EDIT_EVENT_SUCCESS = "Edited Event: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_EVENT = + "The location has been booked for another event at the selected time."; + public static final String MESSAGE_EVENT_CLASH = "The updated time will result in clash with %1$s's schedule."; + + private final Index index; + private final EditEventDescriptor editEventDescriptor; + + /** + * @param index of the event in the filtered event list to edit + * @param editEventDescriptor details to edit the event with + */ + public EditEventCommand(Index index, EditEventDescriptor editEventDescriptor) { + requireNonNull(index); + requireNonNull(editEventDescriptor); + + this.index = index; + this.editEventDescriptor = new EditEventDescriptor(editEventDescriptor); + } + + /** + * Creates and returns a {@code Event} with the details of {@code eventToEdit} + * edited with {@code editEventDescriptor}. + */ + private static Event createEditedEvent(Event eventToEdit, EditEventDescriptor editEventDescriptor) { + assert eventToEdit != null; + + EventName updatedName = editEventDescriptor.getEventName().orElse(eventToEdit.getEventName()); + Description updatedDescription = editEventDescriptor.getDescription().orElse(eventToEdit.getDescription()); + EventDate updatedDate = editEventDescriptor.getDate().orElse(eventToEdit.getDate()); + StartTime updatedStartTime = editEventDescriptor.getStartTime().orElse(eventToEdit.getStartTime()); + EndTime updatedEndTime = editEventDescriptor.getEndTime().orElse(eventToEdit.getEndTime()); + Location updatedLocation = editEventDescriptor.getLocation().orElse(eventToEdit.getLocation()); + Attendees attendees = eventToEdit.getAttendees(); + + return new Event(updatedName, updatedDescription, updatedDate, + updatedStartTime, updatedEndTime, updatedLocation, attendees); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredEventList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToEdit = lastShownList.get(index.getZeroBased()); + Event editedEvent = createEditedEvent(eventToEdit, editEventDescriptor); + + if (editedEvent.getStartTime().compareTo(editedEvent.getEndTime()) >= 0) { + throw new CommandException(EndTime.MESSAGE_INVALID_END_TIME); + } + + if (model.hasEventAfterEdit(eventToEdit, editedEvent)) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); + } + + Set attendeeSet = editedEvent.getAttendees().getAttendeesSet(); + for (String personEmail: attendeeSet) { + if (model.hasClashAfterEdit(eventToEdit, editedEvent, personEmail)) { + throw new CommandException(String.format(MESSAGE_EVENT_CLASH, personEmail)); + } + } + + model.updateEvent(eventToEdit, editedEvent); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + model.commitEventList(); + return new CommandResult(String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditEventCommand)) { + return false; + } + + // state check + EditEventCommand e = (EditEventCommand) other; + return index.equals(e.index) + && editEventDescriptor.equals(e.editEventDescriptor); + } + + /** + * Stores the details to edit the event with. Each non-empty field value will replace the + * corresponding field value of the event. + */ + public static class EditEventDescriptor { + private EventName eventName; + private Description description; + private EventDate date; + private StartTime startTime; + private EndTime endTime; + private Location location; + + public EditEventDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditEventDescriptor(EditEventDescriptor toCopy) { + setEventName(toCopy.eventName); + setDescription(toCopy.description); + setStartTime(toCopy.startTime); + setEndTime(toCopy.endTime); + setLocation(toCopy.location); + setDate(toCopy.date); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(eventName, startTime, endTime, location, description, date); + } + + public Optional getEventName() { + return Optional.ofNullable(eventName); + } + + public void setEventName(EventName eventName) { + this.eventName = eventName; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + public void setDate(EventDate date) { + this.date = date; + } + + public Optional getStartTime() { + return Optional.ofNullable(startTime); + } + + public void setStartTime(StartTime startTime) { + this.startTime = startTime; + } + + public Optional getEndTime() { + return Optional.ofNullable(endTime); + } + + public void setEndTime(EndTime endTime) { + this.endTime = endTime; + } + + public Optional getLocation() { + return Optional.ofNullable(location); + } + + public void setLocation(Location location) { + this.location = location; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditEventDescriptor)) { + return false; + } + + // state check + EditEventDescriptor e = (EditEventDescriptor) other; + + return getEventName().equals(e.getEventName()) + && getDescription().equals(e.getDescription()) + && getStartTime().equals(e.getStartTime()) + && getEndTime().equals(e.getEndTime()) + && getLocation().equals(e.getLocation()) + && getDate().equals(e.getDate()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index beb178e3a3f5..e31768516a7f 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -8,17 +8,19 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all persons in address book whose name contains any of the argument keywords + * or email equals the keyword. + * Keyword and email matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "the specified keywords (case-insensitive) or email matches the keywords (case-insensitive)" + + "and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Example: " + COMMAND_WORD + " alice bob charlie delta@example.com"; private final NameContainsKeywordsPredicate predicate; diff --git a/src/main/java/seedu/address/logic/commands/FindEventCommand.java b/src/main/java/seedu/address/logic/commands/FindEventCommand.java new file mode 100644 index 000000000000..e847c26ca911 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindEventCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.event.EventContainsKeywordsPredicate; +/** + * Finds and lists all events in event whose name and description contains any of the argument keywords + * Keyword matching is case insensitive. + */ +public class FindEventCommand extends Command { + + public static final String COMMAND_WORD = "findEvent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all events whose names or description contain " + + "any of the specified keywords (case-insensitive) " + + "or the description matches the keywords (case-insensitive) " + + "and displays them.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " meeting"; + + private final EventContainsKeywordsPredicate predicate; + + public FindEventCommand(EventContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredEventList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindEventCommand // instanceof handles nulls + && predicate.equals(((FindEventCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/InviteCommand.java b/src/main/java/seedu/address/logic/commands/InviteCommand.java new file mode 100644 index 000000000000..e3c4551f27a6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/InviteCommand.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TO; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.person.Person; + +//@@author jieliangang +/** + * Invites an employee to an event. + */ +public class InviteCommand extends Command { + + public static final String COMMAND_WORD = "invite"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Invites an employee to an event " + + "by the index number used in the displayed employee list and displayed event list.\n" + + "Parameters: PERSON_INDEX (must be a positive integer) " + + PREFIX_TO + "EVENT_INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_TO + "2"; + + public static final String MESSAGE_INVITE_PERSON_SUCCESS = "Invited Employee: %1$s to %2$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This employee already exists in the event."; + public static final String MESSAGE_CLASH_EVENT = "Unable to invite! %1$s clashes with %2$s's schedule."; + + private final Index indexEvent; + private final Index indexPerson; + + public InviteCommand(Index indexPerson, Index indexEvent) { + this.indexPerson = indexPerson; + this.indexEvent = indexEvent; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + List lastShownEventList = model.getFilteredEventList(); + + if (indexPerson.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + if (indexEvent.getZeroBased() >= lastShownEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Person person = lastShownPersonList.get(indexPerson.getZeroBased()); + Event event = lastShownEventList.get(indexEvent.getZeroBased()); + + String personName = person.getName().toString(); + String personEmail = person.getEmail().toString(); + String eventName = event.getEventName().toString(); + + if (!event.isAttendeeEmpty() && event.hasAttendee(personEmail)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + if (model.hasClash(event, personEmail)) { + throw new CommandException(String.format(MESSAGE_CLASH_EVENT, eventName, personName)); + } + + Event updatedEvent = event.createEventWithUpdatedAttendee(personEmail); + + model.updateEvent(event, updatedEvent); + model.commitEventList(); + + return new CommandResult(String.format(MESSAGE_INVITE_PERSON_SUCCESS, personName, eventName)); + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InviteCommand // instanceof handles nulls + && indexEvent.equals(((InviteCommand) other).indexEvent)) + && indexPerson.equals(((InviteCommand) other).indexPerson); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..858f4d887e60 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,25 +1,85 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALL_EVENTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALL_PEOPLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_DEPARTMENT; +import static seedu.address.model.EventModel.PREDICATE_SHOW_ALL_EVENTS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.commons.core.Messages; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; +import seedu.address.model.person.DepartmentContainsKeywordsPredicate; /** - * Lists all persons in the address book to the user. + * Lists all persons in the address book to the user depending on the parameter. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all persons by the given parameter\n" + + "Parameters: " + + PREFIX_LIST_DEPARTMENT + " DEPARTMENT " + + "or " + PREFIX_ALL_PEOPLE + " " + + "or " + PREFIX_ALL_EVENTS + " " + + "or " + PREFIX_ALL + "\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_LIST_DEPARTMENT + " Admin Finance, " + + COMMAND_WORD + " all people"; + public static final String MESSAGE_SUCCESS_PEOPLE = "All people listed!"; + public static final String MESSAGE_SUCCESS_EVENT = "All events listed!"; + public static final String MESSAGE_SUCCESS_ALL = "All people and events listed"; + + public static final String LIST_KEY_DEPARTMENT = "dep"; + public static final String LIST_KEY_ALL = "all"; + public static final String LIST_KEY_PEOPLE = "all people"; + public static final String LIST_KEY_EVENT = "all events"; + + private final String listKey; + private final DepartmentContainsKeywordsPredicate predicate; + + public ListCommand(String sortByParam, DepartmentContainsKeywordsPredicate predicate) { + requireNonNull(sortByParam); + this.predicate = predicate; + listKey = sortByParam; + } @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + + String message = null; + + switch (listKey) { + case (LIST_KEY_DEPARTMENT): model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + + case (LIST_KEY_EVENT): model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + message = MESSAGE_SUCCESS_EVENT; + break; + case (LIST_KEY_PEOPLE): model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + message = MESSAGE_SUCCESS_PEOPLE; + break; + case (LIST_KEY_ALL): model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + message = MESSAGE_SUCCESS_ALL; + break; + default: + } + return new CommandResult(message); + + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCommand + && ((predicate == null && ((ListCommand) other).predicate == null) // instanceof handles nulls + || predicate.equals(((ListCommand) other).predicate))); } } diff --git a/src/main/java/seedu/address/logic/commands/LoginCommand.java b/src/main/java/seedu/address/logic/commands/LoginCommand.java new file mode 100644 index 000000000000..5430dbb502f4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoginCommand.java @@ -0,0 +1,115 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.CommandsParser; +import seedu.address.logic.parser.EmployeeParser; +import seedu.address.logic.parser.ManagerParser; +import seedu.address.model.Model; +import seedu.address.model.person.Email; + + +/** + * Login the person into ProManage + */ +public class LoginCommand extends Command { + public static final String COMMAND_WORD = "login"; + public static final String MESSAGE_INVALID_LOGIN = "Login identity should be either the following:" + + "\nmanager\nemployee\nas EMAIL" + + "\nExample: login manager" + + "\nExample: login as hello@gmail.com"; + + public static final String MESSAGE_SUCCESS = "Successfully login as %s"; + + public static final String MESSAGE_INVALID_DESIGNATION = "Designation of input email is neither 'manager'" + + "nor 'employee'"; + private static final String KEY_MANAGER = "manager"; + private static final String KEY_EMPLOYEE = "employee"; + + private String loginIdentity; + /** + * type == 1 -> argument is an email, there is a need to check if email is present in addressbook and check the + * person's designation to see whether the person is a manager or employee + * type == 2 -> argument is a manager + * type == 3 -> argument is an employee + */ + private final int type; + private Model model; + + public LoginCommand(String arguments, int type) { + requireNonNull(arguments); + requireNonNull(type); + this.loginIdentity = arguments; + this.type = type; + } + + public CommandsParser getParser(Model model) throws CommandException { + this.model = model; + + if (type == 1 && isEmailPresent(loginIdentity)) { + String loginEmail = loginIdentity; + if (isEmailManager(loginIdentity)) { + loginIdentity = KEY_MANAGER; + return new ManagerParser(loginEmail); + } else if (isEmailEmployee(loginIdentity)) { + loginIdentity = KEY_EMPLOYEE; + return new EmployeeParser(loginEmail); + } else { + throw new CommandException(MESSAGE_INVALID_DESIGNATION); + } + } + switch (loginIdentity) { + case KEY_MANAGER: + return new ManagerParser(); + case KEY_EMPLOYEE: + return new EmployeeParser(); + default: + throw new CommandException(MESSAGE_INVALID_LOGIN); + } + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + return new CommandResult(String.format(MESSAGE_SUCCESS, loginIdentity)); + } + + /** + * + * @param loginIdentity input + * @return true or false depending if the email is present in the addressbook + */ + private boolean isEmailPresent(String loginIdentity) { + return model.hasEmail(new Email(loginIdentity)); + } + + /** + * + * @param loginIdentity input + * @return true or false depending if the login identity is manager + */ + private boolean isEmailManager(String loginIdentity) { + return model.getPerson(new Email(loginIdentity)) + .getDesignation().toString().equalsIgnoreCase("manager"); + } + + /** + * + * @param loginIdentity input + * @return true or false depending if the login identity is employee + */ + private boolean isEmailEmployee(String loginIdentity) { + //return model.getPerson(new Email(loginIdentity)).get().getDesignation().equals(new Designation("employee")); + return model.getPerson(new Email(loginIdentity)).getDesignation().toString() + .equalsIgnoreCase("employee"); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand + && ((loginIdentity == null && ((LoginCommand) other).loginIdentity == null) // instanceof handles nulls + || loginIdentity.equals(((LoginCommand) other).loginIdentity))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/LogoutCommand.java b/src/main/java/seedu/address/logic/commands/LogoutCommand.java new file mode 100644 index 000000000000..232fb44363ca --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogoutCommand.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.CommandsParser; +import seedu.address.logic.parser.DefaultParser; +import seedu.address.model.Model; + +//@@author: IcedCoffeeBoy + +/** + * Login the person into ProManage + */ +public class LogoutCommand extends Command { + public static final String COMMAND_WORD = "logout"; + public static final String MESSAGE_SUCCESS = "Successfully logout, priority now is default"; + + + public LogoutCommand() { + } + + public CommandsParser getParser() throws CommandException { + return new DefaultParser(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LogoutCommand); + + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..08134f83d03c 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.EventModel.PREDICATE_SHOW_ALL_EVENTS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import seedu.address.logic.CommandHistory; @@ -20,12 +21,13 @@ public class RedoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canRedoAddressBook()) { + if (!model.canRedo()) { throw new CommandException(MESSAGE_FAILURE); } - model.redoAddressBook(); + model.redo(); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/RemoveCommand.java b/src/main/java/seedu/address/logic/commands/RemoveCommand.java new file mode 100644 index 000000000000..86737fef28e6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveCommand.java @@ -0,0 +1,86 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FROM; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.person.Person; + + +//@@author jieliangang +/** + * Removes an employee from an event. + */ +public class RemoveCommand extends Command { + + public static final String COMMAND_WORD = "remove"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes an employee from an event " + + "by the index number used in the displayed employee list and displayed event list.\n" + + "Parameters: PERSON_INDEX (must be a positive integer) " + + PREFIX_FROM + "EVENT_INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_FROM + "1"; + + public static final String MESSAGE_REMOVE_PERSON_SUCCESS = "Removed Employee: %1$s from %2$s"; + public static final String MESSAGE_ABSENT_PERSON = "This employee is not originally in the event's attendee list."; + public static final String MESSAGE_ATTENDEE_EMPTY = "The event selected has no invited persons."; + + private final Index indexEvent; + private final Index indexPerson; + + public RemoveCommand(Index indexPerson, Index indexEvent) { + this.indexPerson = indexPerson; + this.indexEvent = indexEvent; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + List lastShownEventList = model.getFilteredEventList(); + + if (indexPerson.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + if (indexEvent.getZeroBased() >= lastShownEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Person person = lastShownPersonList.get(indexPerson.getZeroBased()); + Event event = lastShownEventList.get(indexEvent.getZeroBased()); + + String personName = person.getName().toString(); + String personEmail = person.getEmail().toString(); + String eventName = event.getEventName().toString(); + + if (event.isAttendeeEmpty()) { + throw new CommandException(MESSAGE_ATTENDEE_EMPTY); + } else if (!event.hasAttendee(personEmail)) { + throw new CommandException(MESSAGE_ABSENT_PERSON); + } + + Event updatedEvent = event.removePersonFromAttendee(personEmail); + + model.updateEvent(event, updatedEvent); + model.commitEventList(); + + return new CommandResult(String.format(MESSAGE_REMOVE_PERSON_SUCCESS, personName, eventName)); + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveCommand // instanceof handles nulls + && indexEvent.equals(((RemoveCommand) other).indexEvent)) + && indexPerson.equals(((RemoveCommand) other).indexPerson); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index f5e8c1a8722e..7666246c0a06 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -1,6 +1,9 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; import java.util.List; @@ -11,26 +14,47 @@ import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.event.AttendeeContainsEmailPredicate; +import seedu.address.model.event.EventContainsAttendeeAndDatePredicate; +import seedu.address.model.event.TimeType; import seedu.address.model.person.Person; +//@@author jieliangang /** - * Selects a person identified using it's displayed index from the address book. + * Selects an employee identified using it's displayed index from the address book and + * display the selected employee's events on the event list. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Selects the employee identified by the index number used in the displayed person list " + + "and display events the employee is attending. Show events in selected date/month/year if indicated\n" + + "Parameters: PERSON_INDEX (must be a positive integer) " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_YEAR + "YEAR] " + + "[" + PREFIX_MONTH + "MONTH] " + + "If " + PREFIX_DATE + " is used, " + PREFIX_YEAR + " and " + PREFIX_MONTH + " must not be used\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_DATE + "2018-05-24" + " or " + + COMMAND_WORD + " 1 " + PREFIX_MONTH + "05"; public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; private final Index targetIndex; + private final String date; + private final TimeType type; public SelectCommand(Index targetIndex) { this.targetIndex = targetIndex; + this.date = null; + this.type = TimeType.NONE; + } + + public SelectCommand(Index targetIndex, String date, TimeType type) { + this.targetIndex = targetIndex; + this.date = date; + this.type = type; } @Override @@ -43,6 +67,24 @@ public CommandResult execute(Model model, CommandHistory history) throws Command throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + Person person = filteredPersonList.get(targetIndex.getZeroBased()); + String personEmail = person.getEmail().toString(); + + if (type == TimeType.NONE) { + AttendeeContainsEmailPredicate predicateNoDateFilter = new AttendeeContainsEmailPredicate(personEmail); + model.updateFilteredEventList(predicateNoDateFilter); + } else if (type == TimeType.DAY || type == TimeType.MONTH + || type == TimeType.YEAR || type == TimeType.MONTH_AND_YEAR) { + assert date != null; + EventContainsAttendeeAndDatePredicate predicateWithDateFilter = + new EventContainsAttendeeAndDatePredicate(personEmail, date, type); + model.updateFilteredEventList(predicateWithDateFilter); + } else { + throw new CommandException(Messages.MESSAGE_UNKNOWN_COMMAND); + } + + model.sortByDate(); + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); @@ -52,6 +94,10 @@ public CommandResult execute(Model model, CommandHistory history) throws Command public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof SelectCommand // instanceof handles nulls - && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check + && targetIndex.equals(((SelectCommand) other).targetIndex) // state check + && (((date == null && (((SelectCommand) other).date) == null)) // short circuit if both are null + || ((date != null && (((SelectCommand) other).date) != null) + && date.equals(((SelectCommand) other).date))) + && type.equals(((SelectCommand) other).type)); } } diff --git a/src/main/java/seedu/address/logic/commands/SelectEventCommand.java b/src/main/java/seedu/address/logic/commands/SelectEventCommand.java new file mode 100644 index 000000000000..dcf589064dea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SelectEventCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToEventListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventSingleDisplayPredicate; +import seedu.address.model.person.PersonAttendingEventPredicate; + +/** + * Lists all persons in the address book who are attending the specified event. + */ +public class SelectEventCommand extends Command { + + public static final String COMMAND_WORD = "selectEvent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Views all employees who are attending " + + "the selected event indicated by the index number used in the displayed event list. " + + "Also filters the event schedule and only shows the selected event.\n" + + "Parameters: INDEX (must be a positive integer) " + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Selected and showing attendees of event: %1$s"; + + private final Index indexEvent; + private EventSingleDisplayPredicate eventPredicate; + private PersonAttendingEventPredicate personPredicate; + + public SelectEventCommand(Index indexEvent) { + requireNonNull(indexEvent); + + this.indexEvent = indexEvent; + + // Predicates are defined only when the command is executed and the model is accessed. + eventPredicate = null; + personPredicate = null; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredEventList(); + + if (indexEvent.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToShow = lastShownList.get(indexEvent.getZeroBased()); + //eventPredicate = new EventSingleDisplayPredicate(eventToShow); + personPredicate = new PersonAttendingEventPredicate(eventToShow); + + //model.updateFilteredEventList(eventPredicate); + model.updateFilteredPersonList(personPredicate); + + EventsCenter.getInstance().post(new JumpToEventListRequestEvent(indexEvent)); + + return new CommandResult(String.format(MESSAGE_SUCCESS, indexEvent.getOneBased())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof SelectEventCommand)) { + return false; + } + if (eventPredicate == null || personPredicate == null) { + return indexEvent.equals(((SelectEventCommand) other).indexEvent); + } + return (eventPredicate.equals(((SelectEventCommand) other).eventPredicate) + && personPredicate.equals(((SelectEventCommand) other).personPredicate)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ShowMineCommand.java b/src/main/java/seedu/address/logic/commands/ShowMineCommand.java new file mode 100644 index 000000000000..82850447f8d8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowMineCommand.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.event.AttendeeContainsEmailPredicate; + +//@@author: IcedCoffeeBoy + +/** + * List only events associated with the login user + */ +public class ShowMineCommand extends Command { + + public static final String COMMAND_WORD = "showmine"; + + public static final String MESSAGE_SUCCESS = "Sucessfuly show the events associated with you" + + "\nTo view all persons and events please use list all command" + + "\nExample: list all"; + + private String loginIdentity; + + + public ShowMineCommand(String loginIdentity) { + this.loginIdentity = loginIdentity; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + AttendeeContainsEmailPredicate predicateNoDateFilter = new AttendeeContainsEmailPredicate(loginIdentity); + model.updateFilteredEventList(predicateNoDateFilter); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShowMineCommand + && ((loginIdentity == null && ((ShowMineCommand) other).loginIdentity == null) + || loginIdentity.equals(((ShowMineCommand) other).loginIdentity))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 000000000000..bd9649c7c57f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +//@@author: IcedCoffeeBoy +/** + * Sort the event list based on the parameters given + */ +public class SortCommand extends Command { + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sort by the given parameter \n" + + "Parameters: " + + "name, starttime, endtime \n" + + "Example: " + + "sort endtime"; + + public static final String MESSAGE_SUCCESS = "Successfully sort by %s"; + + public static final String KEY_NAME = "name"; + public static final String KEY_STARTTIME = "starttime"; + public static final String KEY_ENDTIME = "endtime"; + public static final String KEY_DATE = "date"; + + private final String key; + + public SortCommand(String sortByParam) { + requireNonNull(sortByParam); + key = sortByParam; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @param history {@code CommandHistory} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + switch (key) { + case (KEY_NAME): + model.sortByName(); + break; + case (KEY_STARTTIME): + model.sortByStartTime(); + break; + case (KEY_ENDTIME): + model.sortByEndTime(); + break; + case (KEY_DATE): + model.sortByDate(); + break; + default: + break; + } + model.commitEventList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, key)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortCommand // instanceof handles nulls + && key.equals(((SortCommand) other).key)); + + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..338a64042107 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.EventModel.PREDICATE_SHOW_ALL_EVENTS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import seedu.address.logic.CommandHistory; @@ -20,12 +21,13 @@ public class UndoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canUndoAddressBook()) { + if (!model.canUndo()) { throw new CommandException(MESSAGE_FAILURE); } - model.undoAddressBook(); + model.undo(); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cde..f44c0c191868 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands.exceptions; +import seedu.address.logic.commands.Command; + /** * Represents an error which occurs during execution of a {@link Command}. */ diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..966e9fdc8bdb 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,6 +2,8 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESIGNATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -13,6 +15,8 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; +import seedu.address.model.person.Department; +import seedu.address.model.person.Designation; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; @@ -31,20 +35,24 @@ 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); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, + PREFIX_DESIGNATION, PREFIX_DEPARTMENT); + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_DEPARTMENT, + PREFIX_DESIGNATION) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } + String newDepartment = rewordDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get()); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Department department = ParserUtil.parseDepartment(newDepartment); + Designation designation = ParserUtil.parseDesignation(argMultimap.getValue(PREFIX_DESIGNATION).get()); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address, department, designation, tagList); return new AddCommand(person); } @@ -57,4 +65,15 @@ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Pre return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } + /** + * + * @param input contains a department word that may be of varying cases + * @return a string whose first letter is capital letter and the rest is lower case + */ + private static String rewordDepartment(String input) { + String lowercase = input.toLowerCase(); + String output = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1); + return output; + } + } diff --git a/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java new file mode 100644 index 000000000000..3ce8403102f8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Description; +import seedu.address.model.event.EndTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Location; +import seedu.address.model.event.StartTime; + +/** + * Parses input arguments and creates a new AddEventCommand object + */ +public class AddEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddEventCommand + * and returns an AddEventCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddEventCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DESCRIPTION, + PREFIX_DATE, PREFIX_LOCATION, PREFIX_START_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DESCRIPTION, + PREFIX_DATE, PREFIX_LOCATION, PREFIX_START_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEventCommand.MESSAGE_USAGE)); + } + + EventName name = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get()); + Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + Location location = ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get()); + EventDate date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + StartTime startTime = ParserUtil.parseStartTime(argMultimap.getValue(PREFIX_START_DATE).get()); + EndTime endTime = ParserUtil.parseEndTime(argMultimap.getValue(PREFIX_END_DATE).get()); + + if (startTime.compareTo(endTime) >= 0) { + throw new ParseException(String.format(EndTime.MESSAGE_INVALID_END_TIME)); + } + + Event event = new Event(name, description, date, startTime, endTime, location); + + return new AddEventCommand(event); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..7253d40b9ace 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,20 +7,29 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddEventCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteEventCommand; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditEventCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.InviteCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SelectEventCommand; +import seedu.address.logic.commands.SortCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; +//TODO: Can be remove + /** * Parses user input. */ @@ -51,15 +60,24 @@ public Command parseCommand(String userInput) throws ParseException { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); + case AddEventCommand.COMMAND_WORD: + return new AddEventCommandParser().parse(arguments); + case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); + case EditEventCommand.COMMAND_WORD: + return new EditEventCommandParser().parse(arguments); + case SelectCommand.COMMAND_WORD: return new SelectCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case DeleteEventCommand.COMMAND_WORD: + return new DeleteEventCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); @@ -67,7 +85,19 @@ public Command parseCommand(String userInput) throws ParseException { return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case SelectEventCommand.COMMAND_WORD: + return new SelectEventCommandParser().parse(arguments); + + case InviteCommand.COMMAND_WORD: + return new InviteCommandParser().parse(arguments); + + case RemoveCommand.COMMAND_WORD: + return new RemoveCommandParser().parse(arguments); case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..ce20fc2cd9e9 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,21 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_DATE = new Prefix("date/"); + public static final Prefix PREFIX_MONTH = new Prefix("m/"); + public static final Prefix PREFIX_YEAR = new Prefix("y/"); + public static final Prefix PREFIX_START_DATE = new Prefix("s/"); + public static final Prefix PREFIX_END_DATE = new Prefix("e/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_DEPARTMENT = new Prefix("dep/"); + public static final Prefix PREFIX_LIST_DEPARTMENT = new Prefix("dep"); + public static final Prefix PREFIX_LOCATION = new Prefix("l/"); + public static final Prefix PREFIX_TO = new Prefix("to/"); + public static final Prefix PREFIX_ALL_PEOPLE = new Prefix("all people"); + public static final Prefix PREFIX_ALL_EVENTS = new Prefix("all events"); + public static final Prefix PREFIX_ALL = new Prefix("all"); + public static final Prefix PREFIX_FROM = new Prefix("from/"); + public static final Prefix PREFIX_DESIGNATION = new Prefix("des/"); + } diff --git a/src/main/java/seedu/address/logic/parser/CommandsParser.java b/src/main/java/seedu/address/logic/parser/CommandsParser.java new file mode 100644 index 000000000000..e112fd2ef362 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CommandsParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author: IcedCoffeeBoy + +/** + * Parses user input. + */ +public abstract class CommandsParser { + + /** + * Used for initial separation of command word and args. + */ + protected static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public abstract Command parseCommand(String userInput) throws ParseException; + + /** + * Returns identity of the parser type + */ + public abstract String getIdentity(); + +} diff --git a/src/main/java/seedu/address/logic/parser/DefaultParser.java b/src/main/java/seedu/address/logic/parser/DefaultParser.java new file mode 100644 index 000000000000..e2f2d609febb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DefaultParser.java @@ -0,0 +1,138 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditEventCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.InviteCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SelectEventCommand; +import seedu.address.logic.commands.ShowMineCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.parser.exceptions.InvalidLogoutException; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class DefaultParser extends CommandsParser { + + public static final String IDENTITY = "Default"; + + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case AddEventCommand.COMMAND_WORD: + return new AddEventCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case EditEventCommand.COMMAND_WORD: + return new EditEventCommandParser().parse(arguments); + + case SelectCommand.COMMAND_WORD: + return new SelectCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case DeleteEventCommand.COMMAND_WORD: + return new DeleteEventCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case FindEventCommand.COMMAND_WORD: + return new FindEventCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case SelectEventCommand.COMMAND_WORD: + return new SelectEventCommandParser().parse(arguments); + + case InviteCommand.COMMAND_WORD: + return new InviteCommandParser().parse(arguments); + + case RemoveCommand.COMMAND_WORD: + return new RemoveCommandParser().parse(arguments); + + case HistoryCommand.COMMAND_WORD: + return new HistoryCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + throw new InvalidLogoutException(IDENTITY); + + case ShowMineCommand.COMMAND_WORD: + return new ShowMineCommandParser().parse(null); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + + public String getIdentity() { + return this.IDENTITY; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java new file mode 100644 index 000000000000..79fc7fef1cdd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteEventCommand + * and returns a Cancel command object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public DeleteEventCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteEventCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteEventCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea1..da1a9eb28f9c 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,6 +3,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESIGNATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -27,12 +29,14 @@ public class EditCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_DEPARTMENT, PREFIX_DESIGNATION, PREFIX_TAG); Index index; @@ -49,12 +53,20 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } + /* if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + throw new ParseException(MESSAGE_EMAIL_NOT_EDITABLE); + }*/ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } + if (argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()) { + String newDepartment = rewordDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get()); + editPersonDescriptor.setDepartment(ParserUtil.parseDepartment(newDepartment)); + } + if (argMultimap.getValue(PREFIX_DESIGNATION).isPresent()) { + editPersonDescriptor.setDesignation(ParserUtil.parseDesignation( + argMultimap.getValue(PREFIX_DESIGNATION).get())); + } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { @@ -79,4 +91,16 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * + * @param input contains a department word that may be of varying cases + * @return a string whose first letter is capital letter and the rest is lower case + */ + private static String rewordDepartment(String input) { + String lowercase = input.toLowerCase(); + String output = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1); + return output; + } + + } diff --git a/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java b/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java new file mode 100644 index 000000000000..cc85f373a5f1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java @@ -0,0 +1,80 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditEventCommand; +import seedu.address.logic.commands.EditEventCommand.EditEventDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditEventCommand object + */ +public class EditEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditEventCommand + * and returns an EditEventCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditEventCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DESCRIPTION, PREFIX_DATE, + PREFIX_START_DATE, PREFIX_END_DATE, PREFIX_LOCATION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditEventCommand.MESSAGE_USAGE), pe); + } + + EditEventDescriptor editEventDescriptor = new EditEventDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editEventDescriptor.setEventName( + ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editEventDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editEventDescriptor.setDate( + ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get())); + } + if (argMultimap.getValue(PREFIX_START_DATE).isPresent()) { + editEventDescriptor.setStartTime( + ParserUtil.parseStartTime(argMultimap.getValue(PREFIX_START_DATE).get())); + } + if (argMultimap.getValue(PREFIX_END_DATE).isPresent()) { + editEventDescriptor.setEndTime( + ParserUtil.parseEndTime(argMultimap.getValue(PREFIX_END_DATE).get())); + } + if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) { + editEventDescriptor.setLocation( + ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editEventDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + + if (!editEventDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditEventCommand.MESSAGE_NOT_EDITED); + } + + return new EditEventCommand(index, editEventDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EmployeeParser.java b/src/main/java/seedu/address/logic/parser/EmployeeParser.java new file mode 100644 index 000000000000..f365931c2533 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EmployeeParser.java @@ -0,0 +1,145 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditEventCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.InviteCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.ShowMineCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.parser.exceptions.InvalidLoginException; +import seedu.address.logic.parser.exceptions.InvalidPrivilegeException; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses user input. + */ +public class EmployeeParser extends CommandsParser { + + public static final String IDENTITY = "Employee"; + + private static String LOGIN_IDENTITY; + + public EmployeeParser() { + LOGIN_IDENTITY = null; + } + + public EmployeeParser(String loginidentity) { + this.LOGIN_IDENTITY = loginidentity; + } + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case SelectCommand.COMMAND_WORD: + return new SelectCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case FindEventCommand.COMMAND_WORD: + return new FindEventCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case InviteCommand.COMMAND_WORD: + return new InviteCommandParser().parse(arguments); + + case HistoryCommand.COMMAND_WORD: + return new HistoryCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case LoginCommand.COMMAND_WORD: + throw new InvalidLoginException(IDENTITY); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommandParser().parse(commandWord); + + case AddCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case AddEventCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case EditCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case EditEventCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case DeleteCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case DeleteEventCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case RemoveCommand.COMMAND_WORD: + throw new InvalidPrivilegeException(IDENTITY); + + case ShowMineCommand.COMMAND_WORD: + return new ShowMineCommandParser().parse(LOGIN_IDENTITY); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + + public String getIdentity() { + return this.IDENTITY; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java b/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java new file mode 100644 index 000000000000..902821ca861d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.EventContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindEventCommand object + */ +public class FindEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindEventCommand + * and returns an FindEventCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindEventCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindEventCommand.MESSAGE_USAGE)); + } + + String[] eventKeywords = trimmedArgs.split("\\s+"); + + return new FindEventCommand(new EventContainsKeywordsPredicate(Arrays.asList(eventKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/InviteCommandParser.java b/src/main/java/seedu/address/logic/parser/InviteCommandParser.java new file mode 100644 index 000000000000..f0ec0a391175 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/InviteCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TO; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.InviteCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author jieliangang +/** + * Parses input arguments and creates a new InviteCommand object + */ +public class InviteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the InviteCommandParser + * and returns a InviteCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public InviteCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TO); + + Index indexPerson; + Index indexEvent; + + try { + indexPerson = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, InviteCommand.MESSAGE_USAGE), pe); + } + + if (argMultimap.getValue(PREFIX_TO).isPresent()) { + try { + indexEvent = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TO).get()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + InviteCommand.MESSAGE_USAGE), pe); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, InviteCommand.MESSAGE_USAGE)); + } + + return new InviteCommand(indexPerson, indexEvent); + + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 000000000000..4af13acbb3b9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,87 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.DepartmentContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns an ListCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ListCommand parse(String userInput) throws ParseException { + List showAll = new ArrayList<>(); + if (parseAll(userInput)) { + return new ListCommand(ListCommand.LIST_KEY_ALL, new DepartmentContainsKeywordsPredicate(showAll)); + } else if (parsePeople(userInput)) { + return new ListCommand(ListCommand.LIST_KEY_PEOPLE, new DepartmentContainsKeywordsPredicate(showAll)); + } else if (parseEvent(userInput)) { + return new ListCommand(ListCommand.LIST_KEY_EVENT, new DepartmentContainsKeywordsPredicate(showAll)); + } else if (parseDep(userInput)) { + String relevantInfo = userInput.replaceAll("(?i)dep", ""); + + if (relevantInfo.equalsIgnoreCase(" ")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + DepartmentContainsKeywordsPredicate predicate = preparePredicate(relevantInfo); + return new ListCommand(ListCommand.LIST_KEY_DEPARTMENT, predicate); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + } + + /** + * Checks if first word of the userInput is equals to "all" + * @return boolean true if it is equal, if not, false + */ + private boolean parseAll(String userInput) { + return userInput.trim().equalsIgnoreCase("all"); + } + + /** + * Checks if first word of the userInput is equals to "all people" + * @return boolean true if it is equal, if not, false + */ + private boolean parsePeople(String userInput) { + return userInput.trim().equalsIgnoreCase("all people"); + } + + /** + * Checks if first word of the userInput is equals to "dep" + * @return boolean true if it is equal, if not, false + */ + private boolean parseDep(String userInput) { + return userInput.trim().split("\\s+")[0] + .equalsIgnoreCase("dep"); + } + + /** + * Checks if first word of the userInput is equals to "all events" + * @return boolean true if it is equal, if not, false + */ + private boolean parseEvent(String userInput) { + return userInput.trim().equalsIgnoreCase("all events"); + } + /** + * Splits the userInput by whitespace and places it into a DepartmentContainsKeywordPredicate + * @return a new predicate + */ + private DepartmentContainsKeywordsPredicate preparePredicate(String userInput) { + String[] departments = userInput.trim().split("\\s+"); + return new DepartmentContainsKeywordsPredicate(Arrays.asList(departments)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/LoginCommandParser.java b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java new file mode 100644 index 000000000000..63c0bae7964f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java @@ -0,0 +1,96 @@ +package seedu.address.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class LoginCommandParser implements Parser { + public static final String KEY_MANAGER = "manager"; + public static final String KEY_EMPLOYEE = "employee"; + private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; + private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; + private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore + private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen + private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; + public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; + + + private static final String MESSAGE_INVALID_LOGIN = "Login identity should be either the following:" + + "\nmanager\nemployee\nas EMAIL" + + "\nExample: login manager" + + "\nExample: login as hello@gmail.com"; + + private static final String MESSAGE_NO_EMAIL = "No email provided"; + + + /** + * Parses user input into command for execution. + * + * @param args full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (isArgsAs(args) && isEmailEmpty(args)) { + throw new ParseException(MESSAGE_NO_EMAIL); + } else if (isArgsAs(args) && isArgsEmail(args)) { + String emailArgs = extractEmail(args); + return new LoginCommand(emailArgs, 1); + } else if (trimmedArgs.equalsIgnoreCase("manager")) { + return new LoginCommand(KEY_MANAGER, 2); + } else if (trimmedArgs.equalsIgnoreCase("employee")) { + return new LoginCommand(KEY_EMPLOYEE, 3); + } else { + throw new ParseException(MESSAGE_INVALID_LOGIN); + } + + } + + private boolean isEmailEmpty(String args) { + return args.trim().split("\\s+").length == 1; + } + + /** + * Checks if first word of the userInput is equals to "as" + * @return boolean true if it is equal, if not, false + */ + private boolean isArgsAs(String userInput) { + return userInput.trim().split("\\s+")[0] + .equalsIgnoreCase("as"); + } + + /** + * Checks if second word of the userInput is equals to an email + * @return boolean true if it is equal, if not, false + */ + private boolean isArgsEmail(String userInput) { + String specificEmail = userInput.trim().split("\\s+")[1]; + return isEmailValid(specificEmail); + } + + /** + * Checks in userInput is of valid email type + * @return boolean true if is of valid type, if not, false + */ + private boolean isEmailValid(String userInput) { + Pattern pattern = Pattern.compile(EMAIL_VALIDATION_REGEX); + Matcher matcher = pattern.matcher(userInput); + return matcher.matches(); + } + + /** + * Extracts email + * @return email + */ + private String extractEmail(String userInput) { + String emailArgs = userInput.trim().split("\\s+")[1]; + return emailArgs; + } +} diff --git a/src/main/java/seedu/address/logic/parser/LogoutCommandParser.java b/src/main/java/seedu/address/logic/parser/LogoutCommandParser.java new file mode 100644 index 000000000000..4e73aba87b14 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LogoutCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author: IcedCoffeeBoy + +/** + * Parses user input. + */ +public class LogoutCommandParser implements Parser { + public static final String MESSAGE_INVALID_LOGOUT = "Invalid logout!" + + "\nlogout should not have any params" + + "\nExample: logout"; + + /** + * Parses user input into command for execution. + * + * @param args no params expected + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public LogoutCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + switch (trimmedArgs) { + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + default: + throw new ParseException(MESSAGE_INVALID_LOGOUT); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ManagerParser.java b/src/main/java/seedu/address/logic/parser/ManagerParser.java new file mode 100644 index 000000000000..a9520cceb86c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ManagerParser.java @@ -0,0 +1,148 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditEventCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.InviteCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SelectEventCommand; +import seedu.address.logic.commands.ShowMineCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.parser.exceptions.InvalidLoginException; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class ManagerParser extends CommandsParser { + + public static final String IDENTITY = "Manager"; + + private static String LOGIN_IDENTITY; + + public ManagerParser() { + LOGIN_IDENTITY = null; + } + + public ManagerParser(String loginidentity) { + this.LOGIN_IDENTITY = loginidentity; + } + + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case AddEventCommand.COMMAND_WORD: + return new AddEventCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case EditEventCommand.COMMAND_WORD: + return new EditEventCommandParser().parse(arguments); + + case SelectCommand.COMMAND_WORD: + return new SelectCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case DeleteEventCommand.COMMAND_WORD: + return new DeleteEventCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case FindEventCommand.COMMAND_WORD: + return new FindEventCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case SelectEventCommand.COMMAND_WORD: + return new SelectEventCommandParser().parse(arguments); + + case InviteCommand.COMMAND_WORD: + return new InviteCommandParser().parse(arguments); + + case RemoveCommand.COMMAND_WORD: + return new RemoveCommandParser().parse(arguments); + + case HistoryCommand.COMMAND_WORD: + return new HistoryCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case LoginCommand.COMMAND_WORD: + throw new InvalidLoginException(IDENTITY); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommandParser().parse(commandWord); + + case ShowMineCommand.COMMAND_WORD: + return new ShowMineCommandParser().parse(LOGIN_IDENTITY); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + + public String getIdentity() { + return this.IDENTITY; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..540a95c4976c 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,7 +9,15 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Description; +import seedu.address.model.event.EndTime; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Location; +import seedu.address.model.event.StartTime; import seedu.address.model.person.Address; +import seedu.address.model.person.Department; +import seedu.address.model.person.Designation; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; @@ -20,11 +28,17 @@ */ public class ParserUtil { + public static final String MONTH_VALIDATION_REGEX = "0[1-9]|1[012]"; + public static final String YEAR_VALIDATION_REGEX = "\\d{4}"; + public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_MONTH = "Input for month is not valid or according to MM format."; + public static final String MESSAGE_INVALID_YEAR = "Input for year is not valid or according to YYYY format."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -95,6 +109,36 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String department} into a {@code Department}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code department} is invalid. + */ + public static Department parseDepartment(String department) throws ParseException { + requireNonNull(department); + String trimmedDepartment = department.trim(); + if (!Department.isValidDepartment(trimmedDepartment)) { + throw new ParseException(Department.MESSAGE_DEPARTMENT_CONSTRAINTS); + } + return new Department(trimmedDepartment); + } + + /** + * Parses a {@code String designation} into a {@code Designation}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code designation} is invalid. + */ + public static Designation parseDesignation(String designation) throws ParseException { + requireNonNull(designation); + String trimmedDesignation = designation.trim(); + if (!Designation.isValidDesignation(trimmedDesignation)) { + throw new ParseException(Designation.MESSAGE_DESIGNATION_CONSTRAINTS); + } + return new Designation(trimmedDesignation); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +165,126 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static EventName parseEventName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!EventName.isValidName(trimmedName)) { + throw new ParseException(EventName.MESSAGE_EVENT_CONSTRAINTS); + } + return new EventName(trimmedName); + } + + /** + * Parses a {@code String description} into a {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_DESCRIPTION_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String location} into a {@code Location}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Location parseLocation(String location) throws ParseException { + requireNonNull(location); + String trimmedLocation = location.trim(); + if (!Location.isValidLocation(trimmedLocation)) { + throw new ParseException(Location.MESSAGE_LOCATION_CONSTRAINTS); + } + return new Location(trimmedLocation); + } + + /** + * Parses a {@code String date} into a {@code Date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static EventDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + if (!EventDate.isValidDate(trimmedDate)) { + throw new ParseException(EventDate.MESSAGE_DATE_CONSTRAINTS); + } + return new EventDate(trimmedDate); + } + + /** + * Parses a {@code String month} into a {@code String}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code month} is invalid. + */ + public static String parseMonth(String month) throws ParseException { + requireNonNull(month); + String trimmedDate = month.trim(); + if (!trimmedDate.matches(MONTH_VALIDATION_REGEX)) { + throw new ParseException(MESSAGE_INVALID_MONTH); + } + return month; + } + + /** + * Parses a {@code String year} into a {@code String}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code year} is invalid. + */ + public static String parseYear(String year) throws ParseException { + requireNonNull(year); + String trimmedDate = year.trim(); + if (!trimmedDate.matches(YEAR_VALIDATION_REGEX)) { + throw new ParseException(MESSAGE_INVALID_YEAR); + } + return year; + } + + /** + * Parses a {@code String startTime} into a {@code StartTime}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static StartTime parseStartTime(String startTime) throws ParseException { + requireNonNull(startTime); + String trimmedStartTime = startTime.trim(); + if (!StartTime.isValidTime(trimmedStartTime)) { + throw new ParseException(StartTime.MESSAGE_TIME_CONSTRAINTS); + } + return new StartTime(trimmedStartTime); + } + + /** + * Parses a {@code String endTime} into a {@code EndTime}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static EndTime parseEndTime(String endTime) throws ParseException { + requireNonNull(endTime); + String trimmedEndTime = endTime.trim(); + if (!EndTime.isValidTime(trimmedEndTime)) { + throw new ParseException(EndTime.MESSAGE_TIME_CONSTRAINTS); + } + return new EndTime(trimmedEndTime); + } + + } diff --git a/src/main/java/seedu/address/logic/parser/ProManageParser.java b/src/main/java/seedu/address/logic/parser/ProManageParser.java new file mode 100644 index 000000000000..9e9b72be1dc4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ProManageParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +//@@author: IcedCoffeeBoy + +/** + * Parses user input. + */ +public class ProManageParser { + /** + * Used for initial separation of command word and args. + */ + + private CommandsParser commandsParser; + public ProManageParser() { + this.commandsParser = new DefaultParser(); + } + + /** + * @param userInput + * @return Command + * @throws ParseException + */ + public Command parseCommand(String userInput, Model model) throws CommandException, ParseException { + Command command = commandsParser.parseCommand(userInput); + if (command instanceof LoginCommand) { + this.commandsParser = ((LoginCommand) command).getParser(model); + } else if (command instanceof LogoutCommand) { + this.commandsParser = ((LogoutCommand) command).getParser(); + } + return command; + } +} diff --git a/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java new file mode 100644 index 000000000000..7cb709710007 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FROM; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author jieliangang +/** + * Parses input arguments and creates a new RemoveCommand object + */ +public class RemoveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommandParser + * and returns a RemoveCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public RemoveCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_FROM); + + Index indexPerson; + Index indexEvent; + + try { + indexPerson = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE), pe); + } + + if (argMultimap.getValue(PREFIX_FROM).isPresent()) { + try { + indexEvent = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_FROM).get()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveCommand.MESSAGE_USAGE), pe); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + + return new RemoveCommand(indexPerson, indexEvent); + + } +} diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java index 565b7f04bfe1..9c2033789e72 100644 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java @@ -1,28 +1,71 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.SelectCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.TimeType; + /** * Parses input arguments and creates a new SelectCommand object */ public class SelectCommandParser implements Parser { + public static final String MESSAGE_INVALID_PREFIX = "m/ or y/ should not be used if date/ is used"; /** * Parses the given {@code String} of arguments in the context of the SelectCommand * and returns an SelectCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ public SelectCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATE, PREFIX_MONTH, PREFIX_YEAR); + + Index index; + try { - Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); + index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe); } + + boolean hasDate = argMultimap.getValue(PREFIX_DATE).isPresent(); + boolean hasMonth = argMultimap.getValue(PREFIX_MONTH).isPresent(); + boolean hasYear = argMultimap.getValue(PREFIX_YEAR).isPresent(); + + if (hasDate && (hasMonth || hasYear)) { + throw new ParseException(MESSAGE_INVALID_PREFIX); + } + + if (!hasDate && !hasMonth && !hasYear) { + return new SelectCommand(index); + } else if (hasDate && !hasMonth && !hasYear) { + EventDate date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + return new SelectCommand(index, date.eventDate, TimeType.DAY); + } else if (hasMonth && !hasYear && !hasDate) { + String month = ParserUtil.parseMonth(argMultimap.getValue(PREFIX_MONTH).get()); + return new SelectCommand(index, month, TimeType.MONTH); + } else if (!hasMonth && hasYear && !hasDate) { + String year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get()); + return new SelectCommand(index, year, TimeType.YEAR); + } else if (hasMonth && hasYear && !hasDate) { + String month = ParserUtil.parseMonth(argMultimap.getValue(PREFIX_MONTH).get()); + String year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get()); + String combined = year + "-" + month; //format year and month into one string + return new SelectCommand(index, combined, TimeType.MONTH_AND_YEAR); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/SelectEventCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectEventCommandParser.java new file mode 100644 index 000000000000..1d43b83442fe --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SelectEventCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.SelectEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectEventCommand object + */ +public class SelectEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SelectEventCommand + * and returns a ViewAttendeeCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public SelectEventCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SelectEventCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectEventCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ShowMineCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowMineCommandParser.java new file mode 100644 index 000000000000..92d99495e57b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ShowMineCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.ShowMineCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author: IcedCoffeeBoy + +/** + * Parses input arguments and creates a new ShowMineCommand object + */ +public class ShowMineCommandParser implements Parser { + + public static final String MESSAGE_INVALID_LOGIN_USER = "Please login with email to use this function" + + "\nExample: login as hello@gmail.com"; + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an ShowMineCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ShowMineCommand parse(String args) throws ParseException { + if (args == null) { + throw new ParseException(MESSAGE_INVALID_LOGIN_USER); + } + return new ShowMineCommand(args); + } +} diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 000000000000..d877de10dd1b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateCommand + * and returns an CreateCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + String key; + switch (trimmedArgs) { + case (SortCommand.KEY_NAME): + key = SortCommand.KEY_NAME; + break; + case (SortCommand.KEY_STARTTIME): + key = SortCommand.KEY_STARTTIME; + break; + case (SortCommand.KEY_ENDTIME): + key = SortCommand.KEY_ENDTIME; + break; + case (SortCommand.KEY_DATE): + key = SortCommand.KEY_DATE; + break; + default: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + return new SortCommand(key); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/InvalidLoginException.java b/src/main/java/seedu/address/logic/parser/exceptions/InvalidLoginException.java new file mode 100644 index 000000000000..a8690f0be617 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/InvalidLoginException.java @@ -0,0 +1,18 @@ +package seedu.address.logic.parser.exceptions; + +//@@author: IcedCoffeeBoy + +/** + * Represents a invalid login error when user is already login + */ +public class InvalidLoginException extends ParseException { + + private static final String MESSAGE_INVALID_LOGIN = "User already login as %s" + + "\nPlease logout to login again" + + "\nExample: logout"; + + public InvalidLoginException(String identity) { + super(String.format(MESSAGE_INVALID_LOGIN, identity)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/InvalidLogoutException.java b/src/main/java/seedu/address/logic/parser/exceptions/InvalidLogoutException.java new file mode 100644 index 000000000000..412e47cccb78 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/InvalidLogoutException.java @@ -0,0 +1,18 @@ +package seedu.address.logic.parser.exceptions; + +//@@author: IcedCoffeeBoy + +/** + * Represents a invalid logout error when no user has login + */ +public class InvalidLogoutException extends ParseException { + + private static final String MESSAGE_INVALID_LOGOUT = "No user currently login. Current privilege is %s" + + "\nTo login please use the login command" + + "\nExample: login manager"; + + public InvalidLogoutException(String identity) { + super(String.format(MESSAGE_INVALID_LOGOUT, identity)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/InvalidPrivilegeException.java b/src/main/java/seedu/address/logic/parser/exceptions/InvalidPrivilegeException.java new file mode 100644 index 000000000000..62bd6d83752b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/InvalidPrivilegeException.java @@ -0,0 +1,19 @@ +package seedu.address.logic.parser.exceptions; + + +//@@author: IcedCoffeeBoy + +/** + * Represents a invalid privilege error when the login privilege + **/ +public class InvalidPrivilegeException extends ParseException { + + private static final String MESSAGE_INVALID_LOGIN = "Insufficient privilege" + + "\nCurrent login as %s" + + "\nPlease login account with higher privilege to use this command"; + + public InvalidPrivilegeException(String identity) { + super(String.format(MESSAGE_INVALID_LOGIN, identity)); + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 7f85c8b9258b..4754c5db7e0d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -5,6 +5,7 @@ import java.util.List; import javafx.collections.ObservableList; +import seedu.address.model.person.Email; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -66,6 +67,25 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a person in the addressbook has the same email. + */ + public boolean hasEmail(Email email) { + requireNonNull(email); + return persons.containsEmail(email); + } + + /** + * + * @param email + * @return the person who has the same email + */ + public Person getPerson(Email email) { + requireNonNull(email); + return persons.getPerson(email); + } + + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -117,4 +137,6 @@ public boolean equals(Object other) { public int hashCode() { return persons.hashCode(); } + + } diff --git a/src/main/java/seedu/address/model/EventList.java b/src/main/java/seedu/address/model/EventList.java new file mode 100644 index 000000000000..457da0153940 --- /dev/null +++ b/src/main/java/seedu/address/model/EventList.java @@ -0,0 +1,174 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.event.Event; +import seedu.address.model.event.UniqueEventList; +import seedu.address.model.person.Person; + +/** + * Wraps all data at the event-list level + * Duplicates are not allowed (by .isSameEvent comparison) + */ +public class EventList implements ReadOnlyEventList { + + private final UniqueEventList events; + + public EventList() { + events = new UniqueEventList(); + } + + /** + * @param toBeCopied + */ + public EventList(ReadOnlyEventList toBeCopied) { + events = new UniqueEventList(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the event list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + this.events.setEvents(events); + } + + /** + * Resets the existing data of this {@code AddressBook} with {@code newData}. + */ + public void resetData(ReadOnlyEventList newData) { + requireNonNull(newData); + this.setEvents(newData.getEventList()); + } + + //// event-level operations + + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + public boolean hasEvent(Event event) { + requireNonNull(event); + return events.contains(event); + } + + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + public boolean hasEventAfterEdit(Event eventToEdit, Event editedEvent) { + requireNonNull(eventToEdit); + requireNonNull(editedEvent); + return events.containsAfterEdit(eventToEdit, editedEvent); + } + + /** + * Adds an event to the address book. + * The event must not already exist in the event list. + */ + public void addEvent(Event e) { + events.add(e); + } + + /** + * Replaces the given event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the event list. + * The identity of {@code editedEvent} must not be the same as another existing event in the event list. + */ + public void updateEvent(Event target, Event editedEvent) { + requireNonNull(editedEvent); + + events.setEvent(target, editedEvent); + } + + /** + * Removes {@code key} from this {@code EventList}. + * {@code key} must exist in the event list. + */ + public void removeEvent(Event key) { + events.remove(key); + } + + /** + * Removes {@code Person} from the Attendee of {@code EventList}. + */ + public void removePersonFromAllEvents(Person person) { + requireNonNull(person); + String personEmail = person.getEmail().toString(); + events.removeAttendee(personEmail); + } + + /** + * Returns true if {@code personEmail}'s schedule clashes with {@code event}. + */ + public boolean hasClash(Event event, String personEmail) { + requireNonNull(event); + requireNonNull(personEmail); + return events.hasClash(event, personEmail); + } + + /** + * Returns true if {@code personEmail}'s schedule clashes with an edited{@code event}. + */ + public boolean hasClashAfterEdit(Event eventBeforeEdit, Event eventAfterEdit, String personEmail) { + requireNonNull(eventBeforeEdit); + requireNonNull(eventAfterEdit); + requireNonNull(personEmail); + return events.hasClashAfterEdit(eventBeforeEdit, eventAfterEdit, personEmail); + } + + public List getSortedEventList() { + return events.getSortedEventList(); + } + + //// util methods + + @Override + public String toString() { + return events.asUnmodifiableObservableList().size() + " events"; + // TODO: refine later + } + + + public ObservableList getEventList() { + return events.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventList// instanceof handles nulls + && events.equals(((EventList) other).events)); + } + + @Override + public int hashCode() { + return events.hashCode(); + } + + @Override + public void sortByName() { + events.sortByName(); + } + + @Override + public void sortByStartTime() { + events.sortByStartTime(); + } + + @Override + public void sortByEndTime() { + events.sortByEndTime(); + } + + @Override + public void sortByDate() { + events.sortByDate(); + } +} + + diff --git a/src/main/java/seedu/address/model/EventModel.java b/src/main/java/seedu/address/model/EventModel.java new file mode 100644 index 000000000000..cd95d1f704fb --- /dev/null +++ b/src/main/java/seedu/address/model/EventModel.java @@ -0,0 +1,113 @@ +package seedu.address.model; + +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.address.model.event.Event; +import seedu.address.model.person.Person; + + +/** + * The API of the Model component. + */ +public interface EventModel { + /** + * {@code Predicate} that always evaluate to true + */ + Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true; + + /** + * Returns the EventList + */ + ReadOnlyEventList getEventList(); + + /** + * Returns true if an event with the same location and time as {@code event} exists in the event list. + */ + boolean hasEvent(Event event); + + /** + * Returns true if an edited event with the same location and time as {@code event} exists in the event list. + */ + boolean hasEventAfterEdit(Event eventToEdit, Event editedEvent); + + /** + * Deletes the given event. + * The event must exist in the address book. + */ + void deleteEvent(Event target); + + /** + * Adds the given event. + * {@code event} must not already exist in the eventList. + */ + void addEvent(Event event); + + /** + * Replaces the given event {@code target} with {@code editedEvent}. + * {@code target} must exist in the event list. + * The event identity of {@code editedPerson} must not be the same as another existing event in the event list. + */ + void updateEvent(Event target, Event editedEvent); + + /** + * Returns an unmodifiable view of the filtered event list + */ + ObservableList getFilteredEventList(); + + /** + * Updates the filter of the filtered event list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredEventList(Predicate predicate); + + /** + * Remove person's name from all event's attendee if present + * This is called when a person is deleted from addressbook + */ + void removePersonFromAllEvents(Person person); + + /** + * Returns true if an {@code event} clashes with any event in the event list. + */ + boolean hasClash(Event event, String personName); + + /** + * Returns true if an {@code event} clashes with any event in the event list. + */ + boolean hasClashAfterEdit(Event eventBeforeEdit, Event eventAfterEdit, String personName); + + /** + * Returns true if the model has previous event list states to restore. + */ + boolean canUndoEventList(); + + /** + * Returns true if the model has undone event list states to restore. + */ + boolean canRedoEventList(); + + /** + * Restores the model's event list to its previous state. + */ + void undoEventList(); + + /** + * Restores the model's event list to its previously undone state. + */ + void redoEventList(); + + /** + * Saves the current event list state for undo/redo. + */ + void commitEventList(); + + void sortByName(); + + void sortByStartTime(); + + void sortByEndTime(); + + void sortByDate(); +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..4ca53b4b4412 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,78 +1,33 @@ package seedu.address.model; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.person.Email; /** * The API of the Model component. */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; +public interface Model extends PersonModel, EventModel { /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void updatePerson(Person target, Person editedPerson); + void resetData(ReadOnlyAddressBook newData, ReadOnlyEventList newEventList); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** Returns true if there are available undo states. */ + boolean canUndo(); - /** - * 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); + /** Undoes previous state. */ + void undo(); - /** - * Returns true if the model has previous address book states to restore. - */ - boolean canUndoAddressBook(); + /** Returns true if there are available redo states. */ + boolean canRedo(); - /** - * Returns true if the model has undone address book states to restore. - */ - boolean canRedoAddressBook(); + /** Redoes next state. */ + void redo(); - /** - * Restores the model's address book to its previous state. - */ - void undoAddressBook(); + /** Undoes a resetData command */ + void undoBothState(); - /** - * Restores the model's address book to its previously undone state. - */ - void redoAddressBook(); + /** Redoes a resetData command */ + void redoBothState(); - /** - * Saves the current address book state for undo/redo. - */ - void commitAddressBook(); + /** check if the email is present */ + boolean hasEmail(Email email); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..7385aaf0e8df 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -2,6 +2,10 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.StateHistoryList.STATE_ADDRESSBOOK; +import static seedu.address.model.StateHistoryList.STATE_BOTH; +import static seedu.address.model.StateHistoryList.STATE_EVENTLIST; +import static seedu.address.model.StateHistoryList.STATE_NONE; import java.util.function.Predicate; import java.util.logging.Logger; @@ -12,6 +16,11 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.EventListChangedEvent; +import seedu.address.model.event.Event; +import seedu.address.model.exceptions.NoRedoableStateException; +import seedu.address.model.exceptions.NoUndoableStateException; +import seedu.address.model.person.Email; import seedu.address.model.person.Person; /** @@ -23,35 +32,149 @@ public class ModelManager extends ComponentManager implements Model { private final VersionedAddressBook versionedAddressBook; private final FilteredList filteredPersons; + private final VersionedEventList versionedEventList; + private final FilteredList filteredEvents; + + private StateHistoryList stateHistoryList; + /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given addressBook, eventList and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyEventList eventList, UserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(addressBook, eventList, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); versionedAddressBook = new VersionedAddressBook(addressBook); filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + + versionedEventList = new VersionedEventList(eventList); + filteredEvents = new FilteredList<>(versionedEventList.getEventList()); + + stateHistoryList = new StateHistoryList(); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new AddressBook(), new EventList(), new UserPrefs()); } @Override - public void resetData(ReadOnlyAddressBook newData) { + public void resetData(ReadOnlyAddressBook newData, ReadOnlyEventList newEventList) { + stateHistoryList.addBothState(); + versionedAddressBook.resetData(newData); + versionedEventList.resetData(newEventList); + indicateAddressBookChanged(); + indicateEventListChanged(); + } + + @Override + public boolean canUndo() { + Integer state = stateHistoryList.getCurrentState(); + switch (state) { + case STATE_ADDRESSBOOK: + return canUndoAddressBook(); + case STATE_EVENTLIST: + return canUndoEventList(); + case STATE_BOTH: + return canUndoEventList() && canUndoAddressBook(); + case STATE_NONE: + // pass-through + default: + return false; + } + } + + @Override + public void undo() { + Integer state = stateHistoryList.getCurrentState(); + switch (state) { + case STATE_ADDRESSBOOK: + undoAddressBook(); + break; + case STATE_EVENTLIST: + undoEventList(); + break; + case STATE_BOTH: + undoBothState(); + break; + case STATE_NONE: + // pass-through + default: + throw new NoUndoableStateException(); + } + } + + @Override + public boolean canRedo() { + Integer state = stateHistoryList.getNextState(); + switch (state) { + case STATE_ADDRESSBOOK: + return canRedoAddressBook(); + case STATE_EVENTLIST: + return canRedoEventList(); + case STATE_BOTH: + return canRedoEventList() && canRedoAddressBook(); + case STATE_NONE: + // pass-through + default: + return false; + } + } + + @Override + public void redo() { + Integer state = stateHistoryList.getNextState(); + switch (state) { + case STATE_ADDRESSBOOK: + redoAddressBook(); + break; + case STATE_EVENTLIST: + redoEventList(); + break; + case STATE_BOTH: + redoBothState(); + break; + case STATE_NONE: + // pass-through + default: + throw new NoRedoableStateException(); + } + } + + @Override + public void undoBothState() { + stateHistoryList.decrementPointer(); + + versionedAddressBook.undo(); indicateAddressBookChanged(); + + versionedEventList.undo(); + indicateEventListChanged(); } + @Override + public void redoBothState() { + stateHistoryList.incrementPointer(); + + versionedAddressBook.redo(); + indicateAddressBookChanged(); + + versionedEventList.redo(); + indicateEventListChanged(); + } + + //=========== Address Book Methods ======================================================================= + @Override public ReadOnlyAddressBook getAddressBook() { return versionedAddressBook; } - /** Raises an event to indicate the model has changed */ + /** + * Raises an event to indicate the addressbook model has changed + */ private void indicateAddressBookChanged() { raise(new AddressBookChangedEvent(versionedAddressBook)); } @@ -62,14 +185,38 @@ public boolean hasPerson(Person person) { return versionedAddressBook.hasPerson(person); } + @Override + public boolean hasEmail(Email email) { + requireNonNull(email); + return versionedAddressBook.hasEmail(email); + } + + @Override + public Person getPerson(Email email) { + requireNonNull(email); + + if (versionedAddressBook.hasEmail(email)) { + return versionedAddressBook.getPerson(email); + } else { + return null; + } + + } + @Override public void deletePerson(Person target) { + requireNonNull(target); + stateHistoryList.addBothState(); + versionedAddressBook.removePerson(target); indicateAddressBookChanged(); + + removePersonFromAllEvents(target); } @Override public void addPerson(Person person) { + stateHistoryList.addAddressBookState(); versionedAddressBook.addPerson(person); updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); indicateAddressBookChanged(); @@ -79,6 +226,7 @@ public void addPerson(Person person) { public void updatePerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); + stateHistoryList.addAddressBookState(); versionedAddressBook.updatePerson(target, editedPerson); indicateAddressBookChanged(); } @@ -114,12 +262,14 @@ public boolean canRedoAddressBook() { @Override public void undoAddressBook() { + stateHistoryList.decrementPointer(); versionedAddressBook.undo(); indicateAddressBookChanged(); } @Override public void redoAddressBook() { + stateHistoryList.incrementPointer(); versionedAddressBook.redo(); indicateAddressBookChanged(); } @@ -129,6 +279,149 @@ public void commitAddressBook() { versionedAddressBook.commit(); } + //=========== EventList Methods ========================================================================== + + @Override + public ReadOnlyEventList getEventList() { + return versionedEventList; + } + + /** + * Raises an event to indicate the eventlist model has changed + */ + private void indicateEventListChanged() { + raise(new EventListChangedEvent(versionedEventList)); + } + + @Override + public boolean hasEvent(Event event) { + requireNonNull(event); + return versionedEventList.hasEvent(event); + } + + @Override + public boolean hasEventAfterEdit(Event eventToEdit, Event editedEvent) { + requireNonNull(eventToEdit); + requireNonNull(editedEvent); + return versionedEventList.hasEventAfterEdit(eventToEdit, editedEvent); + } + + @Override + public void deleteEvent(Event target) { + stateHistoryList.addEventListState(); + versionedEventList.removeEvent(target); + indicateEventListChanged(); + } + + @Override + public void addEvent(Event event) { + stateHistoryList.addEventListState(); + versionedEventList.addEvent(event); + updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + indicateEventListChanged(); + } + + @Override + public void updateEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + stateHistoryList.addEventListState(); + versionedEventList.updateEvent(target, editedEvent); + indicateEventListChanged(); + } + + @Override + public void removePersonFromAllEvents(Person person) { + requireNonNull(person); + + versionedEventList.removePersonFromAllEvents(person); + indicateEventListChanged(); + } + + @Override + public boolean hasClash(Event event, String personEmail) { + requireNonNull(event); + requireNonNull(personEmail); + return versionedEventList.hasClash(event, personEmail); + } + + @Override + public boolean hasClashAfterEdit(Event eventBeforeEdit, Event eventAfterEdit, String personEmail) { + requireNonNull(eventBeforeEdit); + requireNonNull(eventAfterEdit); + requireNonNull(personEmail); + return versionedEventList.hasClashAfterEdit(eventBeforeEdit, eventAfterEdit, personEmail); + } + + @Override + public ObservableList getFilteredEventList() { + return FXCollections.unmodifiableObservableList(filteredEvents); + } + + @Override + public void updateFilteredEventList(Predicate predicate) { + requireNonNull(predicate); + filteredEvents.setPredicate(predicate); + } + + @Override + public void sortByName() { + //stateHistoryList.addEventListState(); + versionedEventList.sortByName(); + } + + @Override + public void sortByStartTime() { + //stateHistoryList.addEventListState(); + versionedEventList.sortByStartTime(); + } + + @Override + public void sortByEndTime() { + //stateHistoryList.addEventListState(); + versionedEventList.sortByEndTime(); + } + + @Override + public void sortByDate() { + //stateHistoryList.addEventListState(); + versionedEventList.sortByDate(); + } + + + //=========== Undo/Redo ================================================================================= + + @Override + public boolean canUndoEventList() { + return versionedEventList.canUndo(); + } + + @Override + public boolean canRedoEventList() { + return versionedEventList.canRedo(); + } + + @Override + public void undoEventList() { + stateHistoryList.decrementPointer(); + versionedEventList.undo(); + indicateEventListChanged(); + } + + @Override + public void redoEventList() { + stateHistoryList.incrementPointer(); + versionedEventList.redo(); + indicateEventListChanged(); + } + + @Override + public void commitEventList() { + versionedEventList.commit(); + } + + //=========== Object Methods ============================================================================= + @Override public boolean equals(Object obj) { // short circuit if same object @@ -144,7 +437,8 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && versionedEventList.equals(other.versionedEventList) + && filteredEvents.equals(other.filteredEvents); } - } diff --git a/src/main/java/seedu/address/model/PersonModel.java b/src/main/java/seedu/address/model/PersonModel.java new file mode 100644 index 000000000000..f02c091ab4c6 --- /dev/null +++ b/src/main/java/seedu/address/model/PersonModel.java @@ -0,0 +1,84 @@ +package seedu.address.model; + +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; + + +/** + * The API of the Model component. + */ +public interface PersonModel { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + + /** Returns the AddressBook */ + ReadOnlyAddressBook getAddressBook(); + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + boolean hasPerson(Person person); + + /** + * Deletes the given person. + * The person must exist in the address book. + */ + void deletePerson(Person target); + + /** + * Adds the given person. + * {@code person} must not already exist in the address book. + */ + void addPerson(Person person); + + /** + * Replaces the given person {@code target} with {@code editedPerson}. + * {@code target} must exist in the address book. + * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + */ + void updatePerson(Person target, Person editedPerson); + + /** Returns an unmodifiable view of the filtered person list */ + ObservableList getFilteredPersonList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonList(Predicate predicate); + + /** + * Returns true if the model has previous address book states to restore. + */ + boolean canUndoAddressBook(); + + /** + * Returns true if the model has undone address book states to restore. + */ + boolean canRedoAddressBook(); + + /** + * Restores the model's address book to its previous state. + */ + void undoAddressBook(); + + /** + * Restores the model's address book to its previously undone state. + */ + void redoAddressBook(); + + /** + * Saves the current address book state for undo/redo. + */ + void commitAddressBook(); + + /** + * + * @param email email input + * @return the person who has the same email as input + */ + Person getPerson(Email email); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyEventList.java b/src/main/java/seedu/address/model/ReadOnlyEventList.java new file mode 100644 index 000000000000..b451c783d059 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyEventList.java @@ -0,0 +1,24 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.event.Event; + +/** + * Unmodifiable view of event list + */ +public interface ReadOnlyEventList { + /** + * Returns an unmodifiable view of the events list. + * This list will not contain any duplicate events. + */ + ObservableList getEventList(); + + void sortByName(); + + void sortByStartTime(); + + void sortByEndTime(); + + void sortByDate(); + +} diff --git a/src/main/java/seedu/address/model/StateHistoryList.java b/src/main/java/seedu/address/model/StateHistoryList.java new file mode 100644 index 000000000000..65a8249acfb4 --- /dev/null +++ b/src/main/java/seedu/address/model/StateHistoryList.java @@ -0,0 +1,110 @@ +package seedu.address.model; + +import java.util.LinkedList; + +import seedu.address.model.exceptions.HistoryStateOutOfBoundsException; + + +/** + * {@code LinkedList} subclass to keep track of AddressBook and EventList changes for undo and redo commands + */ +public class StateHistoryList extends LinkedList { + + static final int STATE_ADDRESSBOOK = 0; + static final int STATE_EVENTLIST = 1; + static final int STATE_BOTH = 2; + static final int STATE_NONE = -1; + + private int pointer; + + StateHistoryList() { + super(); + this.pointer = -1; + } + + private void removeFutureStates() { + subList(pointer + 1, size()).clear(); + } + + /** + * Adds a new AddressBook state + */ + public void addAddressBookState() { + addState(STATE_ADDRESSBOOK); + } + + /** + * Adds a new EventList state to the history list. + */ + public void addEventListState() { + addState(STATE_EVENTLIST); + } + + /** + * Adds a new double AddressBook and EventList state to the history list. + */ + public void addBothState() { + addState(STATE_BOTH); + } + + /** + * Adds a new state to the history list. Undone states in front of the current pointer will be removed. + * + * @param state + */ + private void addState(Integer state) { + // Future states exist + if (pointer < size() - 1 && pointer >= 0) { + removeFutureStates(); + } + add(state); + pointer++; + } + + /** + * Returns the current changed state type at the current pointer position. + */ + public Integer getCurrentState() { + if (size() == 0) { + return STATE_NONE; + } + if (pointer == -1) { + return STATE_NONE; + } + return get(pointer); + } + + /** + * Returns the current changed state type at the current pointer position. + */ + public Integer getNextState() { + if (size() == 0) { + return STATE_NONE; + } + // Has states ahead of current pointer + if (pointer > size() - 2) { + return STATE_NONE; + } + return get(pointer + 1); + } + + /** + * Increment pointer position via {@code redo} + */ + public void incrementPointer() { + if (pointer >= size() - 1) { + throw new HistoryStateOutOfBoundsException("No more states to redo!"); + } + pointer++; + } + + /** + * Increment pointer position via {@code redo} + */ + public void decrementPointer() { + if (pointer < 0) { + throw new HistoryStateOutOfBoundsException("No more states to undo!"); + } + pointer--; + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..c077faed9164 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,8 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path addressBookFilePath = Paths.get("data", "addressbook.xml"); + private Path eventListPath = Paths.get("data", "eventlist.xml"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -34,10 +35,18 @@ public Path getAddressBookFilePath() { return addressBookFilePath; } + public Path getEventlistPath() { + return eventListPath; + } + public void setAddressBookFilePath(Path addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } + public void setEventlistPath(Path eventlistPath) { + this.eventListPath = eventlistPath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java index 227a335045d7..21c8ca958031 100644 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ b/src/main/java/seedu/address/model/VersionedAddressBook.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; +import seedu.address.model.exceptions.NoRedoableStateException; +import seedu.address.model.exceptions.NoUndoableStateException; + /** * {@code AddressBook} that keeps track of its own history. */ @@ -88,22 +91,4 @@ public boolean equals(Object other) { && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) && currentStatePointer == otherVersionedAddressBook.currentStatePointer; } - - /** - * Thrown when trying to {@code undo()} but can't. - */ - public static class NoUndoableStateException extends RuntimeException { - private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); - } - } - - /** - * Thrown when trying to {@code redo()} but can't. - */ - public static class NoRedoableStateException extends RuntimeException { - private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); - } - } } diff --git a/src/main/java/seedu/address/model/VersionedEventList.java b/src/main/java/seedu/address/model/VersionedEventList.java new file mode 100644 index 000000000000..739f758999f2 --- /dev/null +++ b/src/main/java/seedu/address/model/VersionedEventList.java @@ -0,0 +1,94 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.model.exceptions.NoRedoableStateException; +import seedu.address.model.exceptions.NoUndoableStateException; + +/** + * {@code EventList} that keeps track of its own history. + */ +public class VersionedEventList extends EventList { + + private final List eventListStateList; + private int currentStatePointer; + + public VersionedEventList(ReadOnlyEventList initialState) { + super(initialState); + + eventListStateList = new ArrayList<>(); + eventListStateList.add(new EventList(initialState)); + currentStatePointer = 0; + } + + /** + * Saves a copy of the current {@code EventList} state at the end of the state list. + * Undone states are removed from the state list. + */ + public void commit() { + removeStatesAfterCurrentPointer(); + eventListStateList.add(new EventList(this)); + currentStatePointer++; + } + + private void removeStatesAfterCurrentPointer() { + eventListStateList.subList(currentStatePointer + 1, eventListStateList.size()).clear(); + } + + /** + * Restores the event list to its previous state. + */ + public void undo() { + if (!canUndo()) { + throw new NoUndoableStateException(); + } + currentStatePointer--; + resetData(eventListStateList.get(currentStatePointer)); + } + + /** + * Restores the event list to its previously undone state. + */ + public void redo() { + if (!canRedo()) { + throw new NoRedoableStateException(); + } + currentStatePointer++; + resetData(eventListStateList.get(currentStatePointer)); + } + + /** + * Returns true if {@code undo()} has event list states to undo. + */ + public boolean canUndo() { + return currentStatePointer > 0; + } + + /** + * Returns true if {@code redo()} has event list states to redo. + */ + public boolean canRedo() { + return currentStatePointer < eventListStateList.size() - 1; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof VersionedEventList)) { + return false; + } + + VersionedEventList otherVersionedEventList = (VersionedEventList) other; + + // state check + return super.equals(otherVersionedEventList) + && eventListStateList.equals(otherVersionedEventList.eventListStateList) + && currentStatePointer == otherVersionedEventList.currentStatePointer; + } +} diff --git a/src/main/java/seedu/address/model/event/AttendeeContainsEmailPredicate.java b/src/main/java/seedu/address/model/event/AttendeeContainsEmailPredicate.java new file mode 100644 index 000000000000..49b138b8860a --- /dev/null +++ b/src/main/java/seedu/address/model/event/AttendeeContainsEmailPredicate.java @@ -0,0 +1,27 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +//@@author jieliangang +/** + * Tests that a {@code Event}'s {@code Attendee} contains the person email given. + */ +public class AttendeeContainsEmailPredicate implements Predicate { + private final String personEmail; + + public AttendeeContainsEmailPredicate(String personEmail) { + this.personEmail = personEmail; + } + + @Override + public boolean test(Event event) { + return event.hasAttendee(personEmail); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AttendeeContainsEmailPredicate // instanceof handles nulls + && personEmail.equals(((AttendeeContainsEmailPredicate) other).personEmail)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/Attendees.java b/src/main/java/seedu/address/model/event/Attendees.java new file mode 100644 index 000000000000..8d24c27d7efe --- /dev/null +++ b/src/main/java/seedu/address/model/event/Attendees.java @@ -0,0 +1,83 @@ +package seedu.address.model.event; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +//@@author: jieliangang +/** + * Represents the attendees by a list of emails. + */ +public class Attendees { + private Set attendeesSet; + + public Attendees() { + this.attendeesSet = new HashSet<>(); + } + + public Attendees(Set attendeesSet) { + Objects.requireNonNull(attendeesSet); + this.attendeesSet = attendeesSet; + } + + public Attendees(String... emails) { + this(); + attendeesSet = Arrays.stream(emails).collect(Collectors.toSet()); + } + + public Set getAttendeesSet() { + Set attendeesSetCopy = attendeesSet; + return attendeesSetCopy; + } + + /** + * Add email to attendees list + * @param email The new email to be added. + * @return A new copy of updated Attendees. + */ + public Attendees createAttendeesWithAddedEmail(String email) { + Set updatedAttendees = new HashSet<>(this.attendeesSet); + updatedAttendees.add(email); + return new Attendees(updatedAttendees); + } + + /** + * Remove email from attendees list + * @param email The email to be removed. + * @return A new copy of updated Attendees. + */ + public Attendees createAttendeesWithRemovedEmail(String email) { + Set updatedAttendees = new HashSet<>(this.attendeesSet); + updatedAttendees.remove(email); + return new Attendees(updatedAttendees); + } + + /** + * Returns whether set contains email. + * @param email The email to be checked. + */ + public boolean hasPerson(String email) { + return attendeesSet.contains(email); + } + + /** + * Returns whether set is empty. + */ + public boolean isSetEmpty() { + return attendeesSet.isEmpty(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Attendees// instanceof handles nulls + && attendeesSet.equals(((Attendees) other).attendeesSet)); // state check + } + + @Override + public int hashCode() { + return attendeesSet.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/Description.java b/src/main/java/seedu/address/model/event/Description.java new file mode 100644 index 000000000000..a65b2425c8c5 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Description.java @@ -0,0 +1,60 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author: IcedCoffeeBoy +/** + * Represents an Event's description. + * Guarantees: details are present and not null, field values are validated, immutable. + * Description can take any values. + */ +public class Description { + public static final String MESSAGE_DESCRIPTION_CONSTRAINTS = + "Event description can take any values, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String DESCRIPTION_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Address}. + * + * @param description A valid address. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_DESCRIPTION_CONSTRAINTS); + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(DESCRIPTION_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Description // instanceof handles nulls + && value.equals(((Description) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + +} diff --git a/src/main/java/seedu/address/model/event/EndTime.java b/src/main/java/seedu/address/model/event/EndTime.java new file mode 100644 index 000000000000..b19479b1a030 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EndTime.java @@ -0,0 +1,22 @@ +package seedu.address.model.event; + +/** + * Represents a Event's end time in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidTime(String)} + */ +public class EndTime extends Time { + + public static final String MESSAGE_INVALID_END_TIME = + "End time should be later than start time."; + + /** + * Constructs a {@code Name}. + * + * @param time A valid time. + */ + public EndTime(String time) { + super(time); + } + + +} diff --git a/src/main/java/seedu/address/model/event/Event.java b/src/main/java/seedu/address/model/event/Event.java new file mode 100644 index 000000000000..17c0532faabc --- /dev/null +++ b/src/main/java/seedu/address/model/event/Event.java @@ -0,0 +1,285 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +/** + * Represents a Event in the event list. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Event implements Comparable { + + public static final String MESSAGE_DESCRIPTION = " Description: "; + public static final String MESSAGE_LOCATION = " Location: "; + public static final String MESSAGE_DATE = " Date: "; + public static final String MESSAGE_START_TIME = " Start time: "; + public static final String MESSAGE_END_TIME = " End time: "; + + + // Data fields + private final EventName eventName; + private final Description description; + private final EventDate date; + private final StartTime startTime; + private final EndTime endTime; + private final Location location; + private Attendees attendees; + + /** + * Every field besides Attendees must be present not null + */ + + public Event(EventName eventName, Description description, + EventDate date, StartTime startTime, EndTime endTime, Location location) { + requireAllNonNull(eventName, description, startTime, endTime, location); + + this.eventName = eventName; + this.description = description; + this.date = date; + this.startTime = startTime; + this.endTime = endTime; + this.location = location; + attendees = new Attendees(); + + } + + /** + * Overloaded constructor to construct event with existing attendees + */ + + public Event(EventName eventName, Description description, + EventDate date, StartTime startTime, EndTime endTime, Location location, Attendees attendees) { + requireAllNonNull(eventName, description, startTime, endTime, location); + + this.eventName = eventName; + this.description = description; + this.date = date; + this.startTime = startTime; + this.endTime = endTime; + this.location = location; + this.attendees = attendees; + + } + + public EventName getEventName() { + return eventName; + } + + public Description getDescription() { + return description; + } + + public EventDate getDate() { + return date; + } + + public StartTime getStartTime() { + return startTime; + } + + public EndTime getEndTime() { + return endTime; + } + + public Location getLocation() { + return location; + } + + public Attendees getAttendees() { + return attendees; + } + + + /** + * Returns true if both event are at the same location and clashes. + * This defines a weaker notion of equality between two events, indicating that they overlap. + */ + public boolean isSameEvent(Event event) { + if (event == this) { + return true; + } + return event != null + && event.getLocation().equals(getLocation()) + && event.hasClash(this); + } + + /** + * Returns true if both events have the same identity and data fields. + * This defines a stronger notion of equality between two events. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return otherEvent.getEventName().equals(getEventName()) + && otherEvent.getDescription().equals(getDescription()) + && otherEvent.getLocation().equals(getLocation()) + && otherEvent.getStartTime().equals(getStartTime()) + && otherEvent.getEndTime().equals(getEndTime()) + && otherEvent.getDate().equals(getDate()) + && otherEvent.getAttendees().equals(getAttendees()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(eventName, description); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getEventName()) + .append("\n") + .append(MESSAGE_DESCRIPTION) + .append(getDescription()) + .append("\n") + .append(MESSAGE_LOCATION) + .append(getLocation()) + .append("\n") + .append(MESSAGE_DATE) + .append(getDate()) + .append("\n") + .append(MESSAGE_START_TIME) + .append(getStartTime()) + .append("\n") + .append(MESSAGE_END_TIME) + .append(getEndTime()); + return builder.toString(); + } + + /** + * Create a new event with the new email added to the current Attendee. + * + * @param personEmail The person's email to be added to Attendee. + * @return An updated event with the person's email added to the original Attendee. + */ + public Event createEventWithUpdatedAttendee(String personEmail) { + assert personEmail != null; + assert !attendees.hasPerson(personEmail); + Attendees updatedAttendee = attendees.createAttendeesWithAddedEmail(personEmail); + return new Event(eventName, description, date, startTime, endTime, location, updatedAttendee); + } + + + /** + * Create a new event with an existing email removed from the current Attendee. + * + * @param personEmail The person's email to be removed from Attendee. + * @return An updated event with the person's email removed from Attendee. + */ + public Event removePersonFromAttendee(String personEmail) { + assert personEmail != null; + assert attendees.hasPerson(personEmail); + Attendees updatedAttendee = attendees.createAttendeesWithRemovedEmail(personEmail); + return new Event(eventName, description, date, startTime, endTime, location, updatedAttendee); + } + + /** + * Check if personEmail is in attendees of Event. + * + * @param personEmail The person's email to be checked from Attendee. + */ + public boolean hasAttendee(String personEmail) { + assert personEmail != null; + return attendees.hasPerson(personEmail); + } + + + /** + * Check if Attendees of Event is empty. + */ + public boolean isAttendeeEmpty() { + return attendees.isSetEmpty(); + } + + /** + * Returns true if {@code event} time and date clashes + * + */ + public boolean hasClash(Event event) { + if (!event.date.equals(this.date)) { + return false; + } + + StartTime startTime1; + StartTime startTime2; + EndTime endTime1; + EndTime endTime2; + + // Let suffix 1 be the time of event with earlier start time, and suffix 2 be the latter. + if (event.startTime.compareTo(this.startTime) <= 0) { + startTime1 = event.startTime; + endTime1 = event.endTime; + startTime2 = this.startTime; + endTime2 = this.endTime; + } else { + startTime1 = this.startTime; + endTime1 = this.endTime; + startTime2 = event.startTime; + endTime2 = event.endTime; + } + + // Two sufficient and necessary conditions to prove overlap of intervals + boolean doesEvent1BeginEarlierBeforeEvent2Ends = startTime1.compareTo(endTime2) < 0; + boolean doesEvent2BeginBeforeEvent1Ends = startTime2.compareTo(endTime1) < 0; + + return doesEvent1BeginEarlierBeforeEvent2Ends && doesEvent2BeginBeforeEvent1Ends; + } + + + @Override + public int compareTo(Event other) { + int compareValue = this.getEventName().fullName.toLowerCase() + .compareTo(other.getEventName().fullName.toLowerCase()); + if (compareValue == 0) { + return this.getEventName().fullName.compareTo(other.getEventName().fullName); + } + return compareValue; + } + + + /** + * Compare the date between the events + * If both events have the same date then compare their start time + **/ + public int compareDateTo(Event other) { + int compareValue = this.getDate().eventDate.compareTo(other.getDate().eventDate); + if (compareValue == 0) { + return this.getStartTime().compareTo(other.getStartTime()); + } + return compareValue; + } + + /** + * Compare the start time between the events + * If both events have the same start time then compare their date + **/ + public int compareStartTimeTo(Event other) { + int compareValue = this.getStartTime().compareTo(other.getStartTime()); + if (compareValue == 0) { + return this.getDate().eventDate.compareTo(other.getDate().eventDate); + } + return compareValue; + } + + /** + * Compare the end time between the events + * If both events have the same start time then compare their date + **/ + public int compareEndTimeTo(Event other) { + int compareValue = this.getEndTime().compareTo(other.getEndTime()); + if (compareValue == 0) { + return this.getDate().eventDate.compareTo(other.getDate().eventDate); + } + return compareValue; + } +} diff --git a/src/main/java/seedu/address/model/event/EventClashAfterEditPredicate.java b/src/main/java/seedu/address/model/event/EventClashAfterEditPredicate.java new file mode 100644 index 000000000000..c74b498a384f --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventClashAfterEditPredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +/** + * Tests that an edited {@code Event} clashes with a {@code personEmail}'s {@code Event} + */ +public class EventClashAfterEditPredicate implements Predicate { + private final Event eventBeforeEdit; + private final Event eventAfterEdit; + private final String personEmail; + + public EventClashAfterEditPredicate(Event eventBeforeEdit, Event eventAfterEdit, String personEmail) { + this.eventBeforeEdit = eventBeforeEdit; + this.eventAfterEdit = eventAfterEdit; + this.personEmail = personEmail; + } + + @Override + public boolean test(Event event) { + boolean isEventBeforeEdit = event.equals(eventBeforeEdit); + boolean hasAttendee = event.hasAttendee(personEmail); + boolean hasClash = event.hasClash(eventAfterEdit); + return !isEventBeforeEdit && hasAttendee && hasClash; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventClashAfterEditPredicate // instanceof handles nulls + && personEmail.equals(((EventClashAfterEditPredicate) other).personEmail) + && eventBeforeEdit.equals(((EventClashAfterEditPredicate) other).eventBeforeEdit) + && eventAfterEdit.equals(((EventClashAfterEditPredicate) other).eventAfterEdit)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/EventClashPredicate.java b/src/main/java/seedu/address/model/event/EventClashPredicate.java new file mode 100644 index 000000000000..c77c9ef857e1 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventClashPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +/** + * Tests that an {@code Event} clashes a {@code personName}'s {@code Event} + */ +public class EventClashPredicate implements Predicate { + private final Event personEvent; + private final String personEmail; + + public EventClashPredicate(Event personEvent, String personEmail) { + this.personEvent = personEvent; + this.personEmail = personEmail; + } + + @Override + public boolean test(Event event) { + return event.hasAttendee(personEmail) && event.hasClash(personEvent); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventClashPredicate // instanceof handles nulls + && personEmail.equals(((EventClashPredicate) other).personEmail) + && personEvent.equals(((EventClashPredicate) other).personEvent)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/event/EventContainsAttendeeAndDatePredicate.java b/src/main/java/seedu/address/model/event/EventContainsAttendeeAndDatePredicate.java new file mode 100644 index 000000000000..c99fd266ee69 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventContainsAttendeeAndDatePredicate.java @@ -0,0 +1,62 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +//@@author jieliangang +/** + * Tests that a {@code Event}'s {@code EventDate} matches according to input + * and {@code Attendee} contains the person name given. + */ +public class EventContainsAttendeeAndDatePredicate implements Predicate { + public static final String DATE_VALIDATION_REGEX = "^(\\d{4})-(0[1-9]|1[012])$"; + + private final String personEmail; + private final String inputDate; + private final TimeType type; + + public EventContainsAttendeeAndDatePredicate(String personEmail, String inputDate, TimeType type) { + assert type != TimeType.NONE; + assert type != null; + this.personEmail = personEmail; + this.inputDate = inputDate; + this.type = type; + } + + @Override + public boolean test(Event event) { + boolean eventHasAttendee = event.hasAttendee(personEmail); + boolean eventMatchesDate; + + String eventDate = event.getDate().eventDate; + String[] eventTokens = eventDate.split("-", 3); + + switch (type) { + case DAY: + eventMatchesDate = eventDate.matches(inputDate); + break; + case MONTH: + eventMatchesDate = eventTokens[1].matches(inputDate); + break; + case YEAR: + eventMatchesDate = eventTokens[0].matches(inputDate); + break; + case MONTH_AND_YEAR: + assert inputDate.matches(DATE_VALIDATION_REGEX); + String[] tokens = inputDate.split("-", 2); + eventMatchesDate = eventTokens[1].matches(tokens[1]) && eventTokens[0].matches(tokens[0]); + break; + default: + throw new NullPointerException(); + } + return eventHasAttendee && eventMatchesDate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventContainsAttendeeAndDatePredicate // instanceof handles nulls + && personEmail.equals(((EventContainsAttendeeAndDatePredicate) other).personEmail) // state check + && inputDate.equals(((EventContainsAttendeeAndDatePredicate) other).inputDate)) + && type.equals(((EventContainsAttendeeAndDatePredicate) other).type); + } +} diff --git a/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java new file mode 100644 index 000000000000..702b70fa7575 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.event; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class EventContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public EventContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Event event) { + boolean containsInName = keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(event.getEventName().fullName, keyword)); + boolean matchDescription = keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(event.getDescription().value, keyword)); + + return containsInName || matchDescription; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EventContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/event/EventDate.java b/src/main/java/seedu/address/model/event/EventDate.java new file mode 100644 index 000000000000..9eee9b81080c --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventDate.java @@ -0,0 +1,61 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's date in the event list. + * Guarantees: immutable; + */ +public class EventDate { + + public static final String MESSAGE_DATE_CONSTRAINTS = + "Date should only be given in YYYY-MM-DD format and in valid form. Leap year anomalies are not validated."; + + /* + * The date should be valid and in the correct YYYY-MM-DD format. + */ + public static final String DATE_VALIDATION_REGEX = + "^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)" + + "-(0[1-9]|[1-2][0-9]|30)))$"; + + + public final String eventDate; + + /** + * Constructs a {@code EventDate}. + * + * @param date A valid date. + */ + public EventDate(String date) { + requireNonNull(date); + checkArgument(isValidDate(date), MESSAGE_DATE_CONSTRAINTS); + eventDate = date; + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String test) { + return test.matches(DATE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return eventDate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventDate // instanceof handles nulls + && eventDate.equals(((EventDate) other).eventDate)); // state check + } + + @Override + public int hashCode() { + return eventDate.hashCode(); + } + + +} diff --git a/src/main/java/seedu/address/model/event/EventName.java b/src/main/java/seedu/address/model/event/EventName.java new file mode 100644 index 000000000000..3e69c1c6e630 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventName.java @@ -0,0 +1,60 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author: IcedCoffeeBoy +/** + * Represents a Event's name in the event list. + * Guarantees: immutable; + */ +public class EventName { + + public static final String MESSAGE_EVENT_CONSTRAINTS = + "Event name can take any values, and it should not be blank"; + + /* + * The first character of the event name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String NAME_VALIDATION_REGEX = "[^\\s].*"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public EventName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_EVENT_CONSTRAINTS); + fullName = name; + } + + /** + * Returns true if a given string is a valid event name. + */ + public static boolean isValidName(String test) { + return test.matches(NAME_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventName // instanceof handles nulls + && fullName.equals(((EventName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/EventSingleDisplayPredicate.java b/src/main/java/seedu/address/model/event/EventSingleDisplayPredicate.java new file mode 100644 index 000000000000..b8ff50ca204d --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventSingleDisplayPredicate.java @@ -0,0 +1,49 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +/** + * Tests that an {@code Event} matches the singular displayed event + */ +public class EventSingleDisplayPredicate implements Predicate { + private final EventName eventName; + private final EventDate eventDate; + private final Location location; + private final StartTime startTime; + private final EndTime endTime; + + public EventSingleDisplayPredicate(Event event) { + this.eventName = event.getEventName(); + this.eventDate = event.getDate(); + this.location = event.getLocation(); + this.startTime = event.getStartTime(); + this.endTime = event.getEndTime(); + } + + @Override + public boolean test(Event event) { + return event.getEventName().equals(eventName) + && event.getDate().equals(eventDate) + && event.getLocation().equals(location) + && event.getStartTime().equals(startTime) + && event.getEndTime().equals(endTime); + } + + @Override + public boolean equals(Object other) { + // Split up for readability + if (other == this) { // return true if same object + return true; + } + if (!(other instanceof EventSingleDisplayPredicate)) { // instanceof handles nulls + return false; + } + + return eventName.equals(((EventSingleDisplayPredicate) other).eventName) + && eventDate.equals(((EventSingleDisplayPredicate) other).eventDate) + && location.equals(((EventSingleDisplayPredicate) other).location) + && startTime.equals(((EventSingleDisplayPredicate) other).startTime) + && endTime.equals(((EventSingleDisplayPredicate) other).endTime); + } + +} diff --git a/src/main/java/seedu/address/model/event/Location.java b/src/main/java/seedu/address/model/event/Location.java new file mode 100644 index 000000000000..8d39115fe2ab --- /dev/null +++ b/src/main/java/seedu/address/model/event/Location.java @@ -0,0 +1,62 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author: IcedCoffeeBoy +/** + * Represents a Event's location in the event list. + * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)} + */ +public class Location { + public static final String MESSAGE_LOCATION_CONSTRAINTS = + "Event location can take any values, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String LOCATION_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * + * + * + * + * + * @param location A valid address. + */ + public Location(String location) { + requireNonNull(location); + checkArgument(isValidLocation(location), MESSAGE_LOCATION_CONSTRAINTS); + value = location; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidLocation(String test) { + return test.matches(LOCATION_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Location// instanceof handles nulls + && value.toLowerCase().equals(((Location) other).value.toLowerCase())); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + +} diff --git a/src/main/java/seedu/address/model/event/StartTime.java b/src/main/java/seedu/address/model/event/StartTime.java new file mode 100644 index 000000000000..107607c8e7ea --- /dev/null +++ b/src/main/java/seedu/address/model/event/StartTime.java @@ -0,0 +1,18 @@ +package seedu.address.model.event; + +/** + * Represents a Event's start time in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidTime(String)} + */ +public class StartTime extends Time { + + /** + * Constructs a {@code StartTime}. + * + * @param time A valid time. + */ + public StartTime(String time) { + super(time); + } + +} diff --git a/src/main/java/seedu/address/model/event/Time.java b/src/main/java/seedu/address/model/event/Time.java new file mode 100644 index 000000000000..a09b7680e590 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Time.java @@ -0,0 +1,64 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's time in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidTime(String)} + */ +public class Time implements Comparable diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..138a6d2b308b 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -4,5 +4,7 @@ 9482424 hans@example
4th street
+ Admin + manager friends
diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml index 6265778674d3..928953d29bdf 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -5,53 +5,71 @@ 9482424 hans@example.com
4th street
+ Admin + manager Ruth Mueller 87249245 ruth@example.com
81th street
+ Admin + manager
Heinz Kurz 95352563 heinz@example.com
wall street
+ Admin + manager
Cornelia Meier 87652533 cornelia@example.com
10th street
+ Admin + manager
Werner Meyer 9482224 werner@example.com
michegan ave
+ Admin + manager
Lydia Kunz 9482427 lydia@example.com
little tokyo
+ Admin + manager
Anna Best 9482442 anna@example.com
4th street
+ Admin + manager
Stefan Meier 8482424 stefan@example.com
little india
+ Admin + manager
Martin Mueller 8482131 - hans@example.com + hans2@example.com
chicago ave
+ Admin + manager
diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..a6350dbaab64 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -4,5 +4,7 @@ 9482424 hans@example
4th street
+ Admin + manager friends
diff --git a/src/test/java/guitests/guihandles/BrowserPanelHandle.java b/src/test/java/guitests/guihandles/BrowserPanelHandle.java deleted file mode 100644 index bd3633af78f3..000000000000 --- a/src/test/java/guitests/guihandles/BrowserPanelHandle.java +++ /dev/null @@ -1,64 +0,0 @@ -package guitests.guihandles; - -import java.net.URL; - -import guitests.GuiRobot; -import javafx.concurrent.Worker; -import javafx.scene.Node; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; - -/** - * A handler for the {@code BrowserPanel} of the UI. - */ -public class BrowserPanelHandle extends NodeHandle { - - public static final String BROWSER_ID = "#browser"; - - private boolean isWebViewLoaded = true; - - private URL lastRememberedUrl; - - public BrowserPanelHandle(Node browserPanelNode) { - super(browserPanelNode); - - WebView webView = getChildNode(BROWSER_ID); - WebEngine engine = webView.getEngine(); - new GuiRobot().interact(() -> engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { - if (newState == Worker.State.RUNNING) { - isWebViewLoaded = false; - } else if (newState == Worker.State.SUCCEEDED) { - isWebViewLoaded = true; - } - })); - } - - /** - * Returns the {@code URL} of the currently loaded page. - */ - public URL getLoadedUrl() { - return WebViewUtil.getLoadedUrl(getChildNode(BROWSER_ID)); - } - - /** - * Remembers the {@code URL} of the currently loaded page. - */ - public void rememberUrl() { - lastRememberedUrl = getLoadedUrl(); - } - - /** - * Returns true if the current {@code URL} is different from the value remembered by the most recent - * {@code rememberUrl()} call. - */ - public boolean isUrlChanged() { - return !lastRememberedUrl.equals(getLoadedUrl()); - } - - /** - * Returns true if the browser is done loading a page, or if this browser has yet to load any page. - */ - public boolean isLoaded() { - return isWebViewLoaded; - } -} diff --git a/src/test/java/guitests/guihandles/EventCardHandle.java b/src/test/java/guitests/guihandles/EventCardHandle.java new file mode 100644 index 000000000000..ba31a4bb8b90 --- /dev/null +++ b/src/test/java/guitests/guihandles/EventCardHandle.java @@ -0,0 +1,82 @@ +package guitests.guihandles; + +import javafx.scene.Node; +import javafx.scene.control.Label; + +import seedu.address.model.event.Event; + + +/** + * Provides a handle to a event card in the event list panel. + */ +public class EventCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#eventId"; + private static final String EVENT_NAME_FIELD_ID = "#eventName"; + private static final String DESCRIPTION_FIELD_ID = "#description"; + private static final String LOCATION_FIELD_ID = "#eventLocation"; + private static final String DATE_FIELD_ID = "#eventDate"; + private static final String START_TIME_FIELD_ID = "#startTime"; + private static final String END_TIME_FIELD_ID = "#endTime"; + + private final Label idLabel; + private final Label nameLabel; + private final Label descriptionLabel; + private final Label locationLabel; + private final Label dateLabel; + private final Label startTimeLabel; + private final Label endTimeLabels; + + public EventCardHandle(Node cardNode) { + super(cardNode); + + idLabel = getChildNode(ID_FIELD_ID); + nameLabel = getChildNode(EVENT_NAME_FIELD_ID); + descriptionLabel = getChildNode(DESCRIPTION_FIELD_ID); + locationLabel = getChildNode(LOCATION_FIELD_ID); + dateLabel = getChildNode(DATE_FIELD_ID); + startTimeLabel = getChildNode(START_TIME_FIELD_ID); + endTimeLabels = getChildNode(END_TIME_FIELD_ID); + + } + + public String getId() { + return idLabel.getText(); + } + + public String getEventName() { + return nameLabel.getText(); + } + + public String getDescription() { + return descriptionLabel.getText(); + } + + public String getLocation() { + return locationLabel.getText(); + } + + public String getDate() { + return dateLabel.getText(); + } + + public String getStartTime() { + return startTimeLabel.getText(); + } + + public String getEndTime() { + return endTimeLabels.getText(); + } + + + /** + * Returns true if this handle contains {@code event}. + */ + public boolean equals(Event event) { + return getEventName().equals(event.getEventName().fullName) + && getDescription().equals(event.getDescription().value) + && getLocation().equals(event.getLocation().value) + && getDate().equals(event.getDate().eventDate) + && getStartTime().equals(event.getStartTime().time) + && getEndTime().equals(event.getEndTime().time); + } +} diff --git a/src/test/java/guitests/guihandles/EventListPanelHandle.java b/src/test/java/guitests/guihandles/EventListPanelHandle.java new file mode 100644 index 000000000000..9b407acece87 --- /dev/null +++ b/src/test/java/guitests/guihandles/EventListPanelHandle.java @@ -0,0 +1,160 @@ +package guitests.guihandles; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import javafx.scene.Node; +import javafx.scene.control.ListView; +import seedu.address.model.event.Event; + +/** + * Provides a handle for {@code EventListPanel} containing the list of {@code EventCard}. + * Based on PersonListPanel with minor modification and refactoring + */ +public class EventListPanelHandle extends NodeHandle> { + public static final String EVENT_LIST_VIEW_ID = "#eventListView"; + + private static final String CARD_PANE_ID = "#eventCardPane"; + + private Optional lastRememberedSelectedEventCard; + + public EventListPanelHandle(ListView eventListPanelNode) { + super(eventListPanelNode); + } + + /** + * Returns a handle to the selected {@code EventCardHandle}. + * A maximum of 1 item can be selected at any time. + * @throws AssertionError if no card is selected, or more than 1 card is selected. + * @throws IllegalStateException if the selected card is currently not in the scene graph. + */ + public EventCardHandle getHandleToSelectedCard() { + List selectedEventList = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedEventList.size() != 1) { + throw new AssertionError("Event list size expected 1."); + } + + return getAllCardNodes().stream() + .map(EventCardHandle::new) + .filter(handle -> handle.equals(selectedEventList.get(0))) + .findFirst() + .orElseThrow(IllegalStateException::new); + } + + /** + * Returns the index of the selected card. + */ + public int getSelectedCardIndex() { + return getRootNode().getSelectionModel().getSelectedIndex(); + } + + /** + * Returns true if a card is currently selected. + */ + public boolean isAnyCardSelected() { + List selectedCardsList = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedCardsList.size() > 1) { + throw new AssertionError("Card list size expected 0 or 1."); + } + + return !selectedCardsList.isEmpty(); + } + + /** + * Navigates the listview to display {@code event}. + */ + public void navigateToCard(Event event) { + if (!getRootNode().getItems().contains(event)) { + throw new IllegalArgumentException("Event does not exist."); + } + + guiRobot.interact(() -> { + getRootNode().scrollTo(event); + }); + guiRobot.pauseForHuman(); + } + + /** + * Navigates the listview to {@code index}. + */ + public void navigateToCard(int index) { + if (index < 0 || index >= getRootNode().getItems().size()) { + throw new IllegalArgumentException("Index is out of bounds."); + } + + guiRobot.interact(() -> { + getRootNode().scrollTo(index); + }); + guiRobot.pauseForHuman(); + } + + /** + * Selects the {@code EventCard} at {@code index} in the list. + */ + public void select(int index) { + getRootNode().getSelectionModel().select(index); + } + + /** + * Returns the event card handle of an event associated with the {@code index} in the list. + * @throws IllegalStateException if the selected card is currently not in the scene graph. + */ + public EventCardHandle getEventCardHandle(int index) { + return getAllCardNodes().stream() + .map(EventCardHandle::new) + .filter(handle -> handle.equals(getEvent(index))) + .findFirst() + .orElseThrow(IllegalStateException::new); + } + + private Event getEvent(int index) { + return getRootNode().getItems().get(index); + } + + /** + * Returns all card nodes in the scene graph. + * Card nodes that are visible in the listview are definitely in the scene graph, while some nodes that are not + * visible in the listview may also be in the scene graph. + */ + private Set getAllCardNodes() { + return guiRobot.lookup(CARD_PANE_ID).queryAll(); + } + + /** + * Remembers the selected {@code EventCard} in the list. + */ + public void rememberSelectedEventCard() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + lastRememberedSelectedEventCard = Optional.empty(); + } else { + lastRememberedSelectedEventCard = Optional.of(selectedItems.get(0)); + } + } + + /** + * Returns true if the selected {@code EventCard} is different from the value remembered by the most recent + * {@code rememberSelectedEventCard()} call. + */ + public boolean isSelectedEventCardChanged() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + return lastRememberedSelectedEventCard.isPresent(); + } else { + return !lastRememberedSelectedEventCard.isPresent() + || !lastRememberedSelectedEventCard.get().equals(selectedItems.get(0)); + } + } + + /** + * Returns the size of the list. + */ + public int getListSize() { + return getRootNode().getItems().size(); + } +} diff --git a/src/test/java/guitests/guihandles/HelpWindowHandle.java b/src/test/java/guitests/guihandles/HelpWindowHandle.java index ec5fc547aa86..2b49d214ef4d 100644 --- a/src/test/java/guitests/guihandles/HelpWindowHandle.java +++ b/src/test/java/guitests/guihandles/HelpWindowHandle.java @@ -31,4 +31,5 @@ public static boolean isWindowPresent() { public URL getLoadedUrl() { return WebViewUtil.getLoadedUrl(getChildNode(HELP_WINDOW_BROWSER_ID)); } + } diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..b3f4ca210f5b 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -8,27 +8,31 @@ public class MainWindowHandle extends StageHandle { private final PersonListPanelHandle personListPanel; + private final EventListPanelHandle eventListPanel; private final ResultDisplayHandle resultDisplay; private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; public MainWindowHandle(Stage stage) { super(stage); personListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); + eventListPanel = new EventListPanelHandle(getChildNode(EventListPanelHandle.EVENT_LIST_VIEW_ID)); resultDisplay = new ResultDisplayHandle(getChildNode(ResultDisplayHandle.RESULT_DISPLAY_ID)); commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); } public PersonListPanelHandle getPersonListPanel() { return personListPanel; } + public EventListPanelHandle getEventListPanel() { + return eventListPanel; + } + public ResultDisplayHandle getResultDisplay() { return resultDisplay; } @@ -45,7 +49,4 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; - } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index 1789735e49a8..247f4ca9f121 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -19,6 +19,7 @@ public class PersonCardHandle extends NodeHandle { private static final String ADDRESS_FIELD_ID = "#address"; private static final String PHONE_FIELD_ID = "#phone"; private static final String EMAIL_FIELD_ID = "#email"; + private static final String DEPARTMENT_FIELD_ID = "#department"; private static final String TAGS_FIELD_ID = "#tags"; private final Label idLabel; @@ -26,6 +27,7 @@ public class PersonCardHandle extends NodeHandle { private final Label addressLabel; private final Label phoneLabel; private final Label emailLabel; + private final Label departmentLabel; private final List