diff --git a/.gitignore b/.gitignore index 823d175eb670..5cd445ac15b3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,7 @@ lib/* *.iml *.log *.log.* -*.csv -config.json +/config.json src/test/data/sandbox/ preferences.json .DS_Store diff --git a/README.adoc b/README.adoc index 142ae1b17fbd..782b65016413 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,11 @@ -= Address Book (Level 4) +# Invités ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] +https://travis-ci.org/CS2113-AY1819S1-F09-3/main[image:https://travis-ci.org/CS2113-AY1819S1-F09-3/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/aaryamNUS/main[image:https://ci.appveyor.com/api/projects/status/bdt6xr7o98ea332r?svg=true[Build status]] +https://coveralls.io/github/CS2113-AY1819S1-F09-3/main?branch=master[image:https://coveralls.io/repos/github/CS2113-AY1819S1-F09-3/main/badge.svg?branch=master[Coverage Status]] https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://cs2113-ay1819s1-f09-3.github.io/main/[image:https://img.shields.io/badge/Documentation-Online-green.svg[Documentation Link]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,27 +15,53 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* 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. -== Site Map +Invités is desktop application that caters to organisers of different events (such as weddings, orientation camps, networking sessions, product launches, etc). It helps event planners reduce their workload during the planning phase by providing them +a convenient platform to manage guest lists, organise catering services, as well as keep track of payments. Our main target audience are event managers and planners, who wish to utilise a streamlined and efficient approach towards organising their +events. -* <> -* <> -* <> -* <> -* <> +## Our Purpose +To streamline, digitise, and revamp the process of managing events, and benefit both the guests and the planners by doing so. -== Acknowledgements +### Features: +The following list highlights the main features our application provides, ranging from mass messaging to all guests to payment and dietary tracking. -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. -* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] -== Licence : link:LICENSE[MIT] +###### __For an event __ +1. *Overarching detail fields* + + a. Name of event + + b. Date and Time of event + + c. Venue of event + + d. Dress code for event + + e. Number of guests so far + + f. Number of days to event + + +2. *Table of details of guests* + + a. Name of guest + + b. Mobile number of guest + + c. Email address of guest + + d. Dietary Requirement of guest + + e. Ticket number of guest for mass attendance-marking + + f. Status of guest (e.g. VIP) + + g. Status of Payment of guest (e.g. PAID) + +## Acknowledgements +Some parts of this application were adapted from the sample application: https://github.com/se-edu/addressbook-level4[Address Book Level 4] by *_SE-EDU_*. + +#### 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], https://mvnrepository.com/artifact/com.google.zxing/core[ZXing core], https://javaee.github.io/javamail/docs/api/[JavaMail API], https://mvnrepository.com/artifact/commons-io/commons-io[Apache Commons IO] +### Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..38d1b38e4ad8 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "SandhyaGopakumar", + "displayName": "SAN...MAR", + "authorNames": ["SANDHYA\\sandh", "SandhyaGopakumar"] + }, + { + "githubId": "SarahTaaherBonna", + "displayName": "SAR...NNA", + "authorNames": ["SarahTaaherBonna"] + }, + { + "githubId": "aaryamNUS", + "displayName": "SRI...YAM", + "authorNames": ["aaryamNUS"] + }, + { + "githubId": "kronicler", + "displayName": "TAN...ANG", + "authorNames": ["kronicler"] + }, + { + "githubId": "wm28", + "displayName": "TAN...ING", + "authorNames": ["wm28"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..5e60b31751dc 100644 --- a/build.gradle +++ b/build.gradle @@ -75,10 +75,26 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion + // https://mvnrepository.com/artifact/commons-io/commons-io + testImplementation group: 'commons-io', name: 'commons-io', version: '2.4' + testRuntimeOnly group: 'org.testfx', name: 'testfx-internal-java9', version: testFxVersion testRuntimeOnly group: 'org.testfx', name: 'openjfx-monocle', version: 'jdk-9+181' testRuntimeOnly group:'org.junit.vintage', name:'junit-vintage-engine', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + + // https://mvnrepository.com/artifact/javax.mail/mail + compile group: 'javax.mail', name: 'mail', version: '1.4.5' + + // https://mvnrepository.com/artifact/javax.activation/activation + compile group: 'javax.activation', name: 'activation', version: '1.1.1' + + // https://mvnrepository.com/artifact/com.google.zxing/core + compile 'com.google.zxing:core:3.3.3' + + // https://mvnrepository.com/artifact/com.google.zxing/javase + compile 'com.google.zxing:javase:3.3.3' + } shadowJar { @@ -126,7 +142,7 @@ nonGuiTests.dependsOn test task(allTests) // `allTests` implies both `guiTests` and `nonGuiTests` -allTests.dependsOn guiTests +// allTests.dependsOn guiTests allTests.dependsOn nonGuiTests test { @@ -207,8 +223,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': 'Invités', + 'site-githuburl': 'https://github.com/CS2113-AY1819S1-F09-3/addressbook-level4', '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..0a159b6f6ead 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :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} + +Invités was developed by the CS2113T AY18/19 F09-3 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]] [<>] +=== Srivastava Aaryam +image::aaryamnus.png[width="150", align="left"] +{empty}[http://github.com/aaryamNUS[github]] [<>] -Role: Project Advisor +Role: Team lead + +Responsibilities: Mass email communication ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Sandhya Gopakumar +image::sandhyagopakumar.png[width="150", align="left"] +{empty}[http://github.com/SandhyaGopakumar[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: Event details management ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Sarah Taaher Bonna +image::sarahtaaherbonna.png[width="150", align="left"] +{empty}[http://github.com/SarahTaaherBonna[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Guest list management ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Tan Tze Guang +image::kronicler.png[width="150", align="left"] +{empty}[http://github.com/kronicler[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Attendance management ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Tan Wei Ming +image::wm28.png[width="150", align="left"] +{empty}[http://github.com/wm28[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Data sharing ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc deleted file mode 100644 index 5de5363abffd..000000000000 --- a/docs/ContactUs.adoc +++ /dev/null @@ -1,7 +0,0 @@ -= Contact Us -: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. -* *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..52fc7f3c2114 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += Invités - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,14 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2113-AY1819S1-F09-3/main/blob/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team F09-03`      Since: `Aug 2018`      Licence: `MIT` + +== Introduction + +Invités is an application targeted at event managers and planners. The application allows its users to efficiently organise, cater, and manage large events such as weddings, school gatherings, orientation camps, etc. Some of the main features include the ability to send mass emails, keep track of payments, and take note of attendance of the guests. To add to these features, by employing a standardised format, the application is able to take in Comma Separated Values (CSV) files and import the guest list for a particular event. This eliminates the need to key in the guest details manually and subsequently, gives users an alternative if they decide to organise another event using the same guest list. +This guide provides information that will help you get started as a contributor to this project. If you are already a contributor to the project, you will find it useful to refer to this guide. == Setting up @@ -30,7 +35,7 @@ Windows developers are highly recommended to use JDK `9`. + [NOTE] IntelliJ by default has Gradle and JavaFx plugins installed. + -Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plugins` to re-enable them. +Do not disable them. If you have disabled them, please go to `File` > `Settings` > `Plugins` to re-enable them. === Setting up the project in your computer @@ -143,7 +148,7 @@ image::LogicClassDiagram.png[width="800"] The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. -.Component interactions for `delete 1` command (part 1) +.Component interactions for `delete_guest 1` command (part 1) image::SDforDeletePerson.png[width="800"] [NOTE] @@ -151,7 +156,7 @@ Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. -.Component interactions for `delete 1` command (part 2) +.Component interactions for `delete_guest 1` command (part 2) image::SDforDeletePersonEventHandling.png[width="800"] [NOTE] @@ -163,11 +168,11 @@ The sections below give more details of each component. === UI component .Structure of the UI Component -image::UiClassDiagram.png[width="800"] +image::UiComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `EventDetailsPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -189,7 +194,7 @@ link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] . `Logic` uses the `AddressBookParser` 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 command execution can affect the `Model` (e.g. adding a guest) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -201,7 +206,7 @@ image::DeletePersonSdForLogic.png[width="800"] === Model component .Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelComponentClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] @@ -215,7 +220,8 @@ The `Model`, [NOTE] As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + + -image:ModelClassBetterOopDiagram.png[width="800"] +.Structure of the Model Component following OOP +image:ModelComponentClassBetterOopDiagram.png[width="800"] [[Design-Storage]] === Storage component @@ -239,93 +245,398 @@ 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::filter[] +=== Filter feature +==== Current Implementation + +The filter mechanism is facilitated by `VersionedAddressBook`. +Given below is an example usage scenario and how the filter mechanism behaves at each step. + +Step 1. The user launches the application for the first time. The `VersionedAddressBook` +will be initialized with the initial address book state. + +Step 2. The user executes `filter t/vegan pa/paid` command to obtain a list of people +who are Vegan *and* have paid. +The `filter` command calls `Model#getFilteredPersonList()`. + +The following sequence diagram shows how the filter operation works: + +.Filter Sequence Diagram +image::FilterSequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: How filter executes + +* **Alternative 1 (current choice):** User has to include prefixes when using +filter command. +** Pros: Will use less memory (e.g. for `t/`, just search through the tags field directly). +** Cons: We must ensure that the user includes the prefix of each +individual keywords and check that the prefixes are correct. + +* **Alternative 2:** User just enters keywords without prefixes. +** Pros: Easy to implement. +** Cons: May have performance issues (e.g. to find guests with a particular tag, +the application will have to go through the payment and attendance fields, before going +through the tag field). +// end::filter[] + +// tag::find[] +=== Find feature ==== 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 find mechanism is facilitated by `VersionedAddressBook`. +Given below is an example usage scenario and how the find mechanism behaves at each step. -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +Step 1. The user launches the application for the first time. The `VersionedAddressBook` +will be initialized with the initial address book state. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Step 2. The user executes `find n/Alex p/92743824 e/johndoe@gmail.com` command to obtain +a list of people who have the name `Alex`, phone number `92743824` *or* email address +`johndoe@gmail.com`. +The `find` command calls `Model#getFilteredPersonList()`. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +The following sequence diagram shows how the find operation works: + +.Find Sequence Diagram +image::FindSequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: How find executes + +* **Alternative 1 (current choice):** User has to include prefixes when using +find command. +** Pros: Will use less memory (e.g. for `e/`, just search through the email field directly). +** Cons: We must ensure that the user includes the prefix of each + individual keywords and check that the prefixes are correct. +* **Alternative 2:** User just enters keywords without prefixes. +** Pros: Easy to implement. +** Cons: May have performance issues (e.g. to find a guest + with a particular email address, the application will have to + go through the name and phone number fields, before going through the email field). +// end::find[] + +// tag::event[] +=== Add/Delete Event feature +==== Current Implementation + +The add_event and delete_event mechanisms are facilitated by `VersionedAddressBook`. +Given below is an example usage scenario and how the add_event and delete_event mechanisms behave at each step. 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. -image::UndoRedoStartingStateListDiagram.png[width="800"] +Step 2. The user executes `add_event n/Wedding d/18/10/2019 v/Mandarin Hotel st/10:00 AM t/ClassicTheme` command to add in details about the event they are currently organising. +The `add_event` command calls `Model#addEvent()` to add in the event details. +It calls 'Model#commitAddressBook()' which saves the modified state of the address book after the command executes in the `addressBookStateList`. +The `currentStatePointer` is shifted to the newly inserted address book state. -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. +[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`. -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] +[NOTE] +If the user has added in the details of the event they are organising, then another set of event details should not be stored. +The `add_event` command uses `Model#hasEvent()` to check if this is the case. If so, it will return an error to the user. -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. After the event has taken place, the user decides to organise another event with the same guest list and deletes the event details using the 'delete_event' command. +The `delete_event` command calls `Model#deleteEvent` to delete the event's details. +The command also calls Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +[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`. + +[NOTE] +If the user has not added in the details of an event, then there are no specific event details to delete. +The `delete_event` command uses `Model#hasEvent()` to check if this is the case. If so, it will return an error to the user. + +The following sequence diagram shows how the add_event operation works: + +.Sequence diagram for add_event command that adds event details. +image::AddEventSequenceDiagram.png[width="800" ] + +The following sequence diagram shows how the delete_event operation works: + +.Sequence diagram for delete_event command that deletes event details. +image::DeleteEventSequenceDiagram.png[width="800" ] + +==== Design considerations +===== Aspect: Creation of the Event component +*** Alternative 1(current choice): Create an 'Event' with date, name, start time, venue and tag attributes and an aggregation association with 'AddressBook' wherein 'AddressBook' contains an 'Event' object. +** Pros: It is easier to use and test. +** Cons: The user can only organise 1 event at a time. + +*** Alternative 2: Create an 'Event' having an aggregation association with 'VersionableAddressBook'. Create an 'InvitesBook' having an aggregation association with 'Event'. +** Pros: It allows the user to organise multiple events and manage multiple event-specific guest lists. +** Cons: The 'undo' and 'redo' commands are affected. + +=== Edit Event feature +==== Current Implementation + +The edit_event mechanism is facilitated by `VersionedAddressBook`. +Given below is an example usage scenario and how the edit_event mechanism behaves at each step. + +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. + +Step 2. The user executes `add_event n/Wedding d/8/12/2019 v/Hilton st/10:00 AM` command to add in details about the event they are currently organising. + +Step 3. Due to a sudden change of plans, the user wishes to change the event's date and venue. +The user executes 'edit_event d/10/12/2019 v/Novotel' command. The 'edit_event' command calls `Model#updateEvent' to update the event's details. +The command also calls Model#commitAddressBook()` which saves the modified address book state into the `addressBookStateList`. [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`. -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. +[NOTE] +If the user has not added in the details of an event, then there are no specific event details to edit. +The `edit_event` command uses `Model#hasEvent()` to check if this is the case. If so, it will return an error to the user. + +The following sequence diagram shows how the edit_event operation works: + +.Sequence diagram for edit_event command that edits the event details +image::EditEventSequenceDiagram.png[width="800"] + +// end::event[] + +// tag::emailimplementation[] +=== Email Functionality + +==== High Level Algorithm Design +The email functionality, comprising of the commands `email`, `emailAll`, and `emailSpecific`, allows the user to send individual and mass emails to their guests. +Moreover, all three commands will open a popup window to prompt the user to enter their relevant details, namely their email address and password, +as well as the email subject and message. Currently `Gmail` is the only supported domain for the user's email address, however the implementation of this function +easily allows for more domains to be supported. + +Additionally, the *main location of the email functionality's implementation* resides within the *Logic* component, with an additional *UI* component to allow the user to input their details. + +.EmailAll Class Diagram +image::EmailAllClassDiagram.png[width="700"] + +The figure above captures the main interactions between all classes when the user executes an `emailAll` command. + + * The command depends on the class `EmailPasswordAuthenticator` for verification of the user's email credentials and the abstract class `Email`. The `Email` class creates the email object to be sent, as well as establishes a connection with Gmail's SMTP server via `Properties`. + * Moreover, the creation of the UI window is facilitated by the class `EmailWindow`, which also returns the email data once you input your details. + * Furthermore, the email object is created thanks to the `MimeMultiPart` class, and comprises of two main parts: + ** The first part is the text input the user provided in the UI window, which is used by `MimeMessage` and `MimeBodyPart`, both of which implement the interface `MimePart`. + ** The second part is the QR code which acts as the guest's ticket and is generated by `QrUtil`. Note that currently only the `email` command generates the QR code. + * Finally, the email, with all its contents, is sent via the abstract class `Transport`. + +==== Command Mechanism +.MailCommand Activity Diagram +image::MailCommandActivityDiagram.png[width="700"] + +The figure above represents an activity diagram when the user executes the _email_ command. As you can see, the command involves *error checking*, followed by *creating an email object*, and finally *sending* this email to an individual guest. Furthermore, this procedure follows the interactions described earlier. + +==== Design Considerations +===== Aspect: Creating an EmailWindow for user input +*** Current choice: Creating the UI component directly from the abstract class Email. +** Pros: Reduces dependencies with other UI components and increases cohesion with Email class. +** Cons: Does not use a controller to create the UI component, it is called directly from logic component Email. + +*** Alternative: Create a controller for showing `EmailWindow`. +** Pros: Will fit with the software architecture of the codebase. +** Cons: Current model will have to be changed to allow feed back of user input. + +===== Aspect: Implementing email verification of guest email addresses +*** Current choice: Email addresses are only checked for validity, not whether they exist. +** Pros: Efficient way to send mass emails without slowing down the program for verification. +** Cons: User will be unaware if the guest email address does not exist, which may affect communication. + +*** Alternative: Implement email validation for all possible email domains. +** Pros: User is able to check which guest email addresses do not exist. +** Cons: Would make the codebase bulkier and require multiple email domain configurations. + +// end::emailimplementation[] + +// tag::importexport[] + +=== Import/Export Command +==== Current Implementation + +===== High level overview of the class hierarchy +The import and export command enables batch importation and exportation of people into and out of the guest list. Additionally, the import command will create a popup window to show the errors during import only if there are any. The commands currently only support comma-separated value file format (CSV), however, it is designed to easily support other formats such as VCard in the future. + +The implementation of the import and export feature mainly resides under the logic component of the application. The import command involves an additional user interface (UI) component that shows import errors. + +The Import/Export feature is facilitated by the `AdaptedPerson`,`PersonConverter` and `SupportedFile` interfaces. They provide the behaviour specifications so that the Import/Export command will be able to operate without knowing the underlying implementations. + +* `AdaptedPerson` represents a person in the respective file format. It requires the following method. +** `AdaptedPerson#getFormattedString()`: returns the string representation of the person according to the particular file format. +* `SupportedFile` represents a supported file that is able to read and write `AdaptedPerson` s' to the actual file on the computer. Here are some of its key methods +** `SupportedFile#readAdaptedPersons()`: Returns all person in the form of `AdaptedPerson` s from the file +** `SupportedFile#writeAdaptedPersons()`: Writes all `AdaptedPerson` to the file +* `PersonConverter` represents a person converter that is able to convert between `Person` s and `AdaptedPerson` s. Here are some of its key methods. +** `PersonConverter#encodePerson()`: Encodes a `Person` object and returns the corresponding `AdaptedPerson` object +** `PersonConverter#decodePerson()`: Decodes an `AdaptedPerson` object and returns corresponding `Person` object + +To support the import/export of CSV files, `CsvAdaptedPerson`,`CsvPersonConverter` and `CsvFile` implements the above mentioned interfaces. + +For the import command, the popup window to show errors encountered is facilitated by the `ImportError` and `ImportReportWindow` classes. + +* `ImportError` represents an error encountered during the import command. It stores the actual CSV formatted person and its associated error message. +* `ImportReportError` is the controller class of the popup window that will display all `ImportError` s encountered during the execution of an import command. + +The following class diagrams shows the relationship between the classes and interfaces mentioned above. + +.Import/Export Command Class Diagram +image::ImportExportClassDiagram.PNG[width="800"] + +===== Command mechanism + +The import command will first read the csv file and loop through all the guest data and add them into the model. When application encounters a particular guest in CSV file which fails to be converted or is already an existing guest, an `ImportError` will be created. These `ImportError` object will be added in a list within the import command. + +After the command completes the importation of all guests in the guest list, if there are unsuccessful imports, it will trigger a `ShowImportReportEvent` which will display the errors -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +The following sequence diagram shows how the Import operation works: + +.Import Command Sequence Diagram +image::ImportCommandSequenceDiagram.PNG[width="800"] + +The `ShowImportReportEvent` will be handled by the `MainWindow` according to the following sequence diagram below. + +.Import Report Window Sequence Diagram +image::ImportReportWindowSequenceDiagram.png[width="500"] + +''' + +The export command will only export the currently filtered guest list by calling `Model#getFilteredPersonList`. This enables greater flexibility as it provides a way for users to select specific groups of people to export. The following sequence diagram shows how the export operation works: + +.Export Command Sequence Diagram +image::ExportCommandSequenceDiagram.PNG[width="800"] + +==== Design Considerations + +===== Aspect: Implementing decoding/encoding functionality in Import/Export command + +*** Alternative 1 (current choice): import & export command be able to do accept a general `PersonConverter` +** Pros: Reduction in code duplication when supporting other file-formats in the future. Easier to mock and do unit tests. +** Cons: More complicated to implement. + +*** Alternative 2: Each supported format has its own command which knows how to do the required conversion +** Pros: We do not need to check for the required import/export format required. +** Cons: Higher testing overhead for possible numerous types of export & import command. Duplicated boilerplate code. + +===== Aspect: Implementing the reading/writing of file functionality in Import/Export command + +*** Alternative 1: Abstract the writing/reading of files into separate classes, `SupportedFile` interface and `CsvFile` class (current choice) +** Pros: Able to add support for other file formats with changing existing code. +** Cons: Increased code complexity. + +*** Alternative 2: Use a utility class with static methods +** Pros: Simple to implement. +** Cons: Violates open-close principle. Code will only work for CSV files. High coupling with the import/export command. Impossible to mock, decreases the testability of the import/export commands. + +// end::importexport[] + +// tag::markunmark[] +=== Mark/Unmark Command +The mark/unmark mechanism is facilitated by `Model`. +Given below is an example usage scenario and how the mark/unmark command executes at each step: + +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. + +Step 2. The user executes the command `import guestlist.csv` to import a list of guest and add them to the current state of the `AddressBook`. [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. +Alternatively, the user can execute the command `add_guest n/John Doe p/98765432 e/johnd@gmail.com pa/PAID a/ABSENT u/00001 t/NORMAL` to create an instance of one guest and add them to the current state of `AddressBook`. -The following sequence diagram shows how the undo operation works: +Step 3. The user will execute the command `mark 00001` to set the attendance of the `Person` to `PRESENT`. -image::UndoRedoSequenceDiagram.png[width="800"] +Step 4. An instance of `getPersonList` is retrieved from the `model` using `MODEL#getAddressBook#getPersonList`. A linear search is then executed on the `getPersonList` to find a `Person` with the same Unique ID (UID) as `00001`. + +[NOTE] +If there is no matching UID found, a `COMMANDEXCEPTION` will be thrown to indicate nobody in the list has the phone number. +If there is more than one instance of the same UID, a `COMMANDEXCEPTION` will be thrown to indicate that there are more than one instance of the same UID. In this case, `edit_guest` will not work. The user has to delete the entry and `add_guest` for the deleted entry. -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +Step 5. After retrieving the information from the discovered `Person`, another `Person` is created with the same fields with the exception of the attendance field being changed from `ABSENT` to `PRESENT`. + +Step 6. Finally, the entry is updated using `MODEL#updatePerson` to transfer the new information into the `Model` before `commitAddressBook` is executed to save the state of the `AddressBook`. + +The following sequence diagram shows how the `mark` operation works: + +.Mark Command Sequence Diagram +image::MarkCommandSequenceDiagram.png[width="800"] + +The following sequence diagram shows how the `ummark` operation works: + +.Unmark Command Sequence Diagram +image::UnmarkCommandSequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: How mark/unmark executes + +*** Alternative 1 (current choice): access the `PersonList` model to execute marking of attendance. +** Pros: Able to mark the attendance of anybody within the list even when it is filtered. Prevents the bug of a person filtering the list to only show a single instance of the multiple similar UID to mark their attendance. +** Cons: Some changes might be hard to detect when displaying a filtered list. + +*** Alternative 2: access the `filteredPersonList` model to execute the marking of attendance. +** Pros: Able to work with a smaller set of people in the list. Changes made are immediately observable. +** Cons: Introduces bugs such as avoiding the similar UID check within the current instance of the guest list. If the filtered list does not immediately have the UID that needs to be used, the user has to call `list` command first. Increasing the chance of human error in the system. + +// end::markunmark[] + +// tag::generateuid[] + +=== Extension made to add_guest: Generate Unique ID (UID) on creation of an entry into the list +This feature is facilitated by the usage of the `Model`. + +Given below is an example of the execution of the added functionality of the `add_guest` command. + +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. + +Step 2. The user executes the `add_guest` command with the following non-null fields: `add_guest n/John Doe p/98765432 e/johnd@gmail.com pa/PAID a/ABSENT u/00000 t/NORMAL`. + +Step 3. The `addCommandParser` extracts the relevant information from the command and returns a `Person` model that was built with the given arguments. + +Step 4. The `add_guest.execute` function receives the generated `Person` model and checks for duplicates of the same `Person` as well as duplicates of the same `UID` in the `VersionedAddressBook`. [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 the checks fail, `COMMANDEXCEPTION` will be thrown to indicate the existence of more than one of the same `UID` or `Person` -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The function will test the equality between the `UID` given in the generated `Person` and the `DEFAULT_TO_GENERATE_UID` which is `u/00000`. From here the program will do one of 2 different things. -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] +Step 6a. If the `UID` given is not the same as `DEFAULT_TO_GENERATE_UID`: + + The program will treat this as a user defined UID and add the `Person` into the `Model` without generating a new UID. -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. +Step 6b. Alternatively, If the `UID` input in step 2 is the same as `DEFAULT_TO_GENERATE_UID`: + + The program will create a new `Person` model with the same arguments with the exception of `UID`. The `UID` is then generated randomly in the `generateUid()` function. + Another check is done to ensure that the `UID` does not already exist in the `VersionedAddressBook`. + This is done in a loop to ensure the `UID` is unique before adding the `Person` into the `Model` by executing `addPerson()`. -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] +Step 7. After adding the `Person` into the `Model`, the program commits the changes by executing `commitAddressBook` to save the state of the `AddressBook`.. -The following activity diagram summarizes what happens when a user executes a new command: +The following sequence diagram shows how the `add_guest` command works: -image::UndoRedoActivityDiagram.png[width="650"] +.Add Command Sequence Diagram +image::AddCommandSequenceDiagram.png[width="800"] ==== Design Considerations +===== Aspect: Whether to only generate UID or to allow the user to define UID -===== Aspect: How undo & redo executes +*** Alternative 1 (current choice): give the user the freedom to choose between defining their own `UID` or letting the program generate one itself. +** Pros: Able to write tests for the command that will not fail. A company or event may already be using some sort of unique identification, this can be used by the event planner to uniquely identify attendees. If there is none, the user can just let the program generate one for them. +** Cons: Increased code complexity -* **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. +*** Alternative 2: only let the user define the `UID` for all attendees of the event. +** Pros: Decreased code complexity +** Cons: User will face great difficulty in creating UID for a large group of people if there is no unique identifier available for the attendees already. -===== Aspect: Data structure to support the undo/redo commands +*** Alternative 3: only generate the `UID` for all the attendees in the list, user input for the UID is not allowed. +** Pros: Decreased code complexity +** Cons: Unable to write meaningful tests if the the actual output is going to be random. Users may already have a way to uniquely identify the attendees to the event but the event planner cannot make use of that method. -* **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[] +// end::generateuid[] // tag::dataencryption[] === [Proposed] Data Encryption -_{Explain here how the data encryption feature will be implemented}_ +_{We plan on implementing a data encryption feature such that when the user chooses to, the data stored in the addressbook will be encrypted and display ceases to show all information.}_ // end::dataencryption[] +=== [Proposed] Guest Email Verification + +_{We plan on implementing a guest email verification system, that lets the user know which of their guest email addresses exist, for any email domain.}_ + === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -534,428 +845,540 @@ b. Require developers to download those libraries manually (this creates extra w [[GetStartedProgramming]] [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 <>. +* 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 -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. +*Value proposition*: manage contacts faster than a typical mouse/GUI driven app -[[GetStartedProgramming-EachComponent]] -=== Improving each component +[appendix] +== User Stories -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). +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[discrete] -==== `Logic` component +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |event planner |be able to mark attendance of guests easily |minimise holdup as much as possible -*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. +|`* * *` |event planner |be able to view event details and countdown to event date|take note of important details closer to the event date. -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +|`* * *` |event planner |be able to delete event details easily |organise another event with the same guest list but different event details. -. 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. -**** +|`* * *` |event planner |be able to send mass emails to guests |remind them about the event -[discrete] -==== `Model` component +|`* * *` |event planner |be able to tag guests with specific labels |take note of any extra details if necessary -*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. +|`* * *` |event planner specialising in large events such as weddings|tag all my guests in the list at once |save a lot of time and increase efficiency, as opposed to editing the tags of each individual guest -[TIP] -Do take a look at <> before attempting to modify the `Model` component. +|`* * *` |event planner specialising in large weddings |be able to track the guest list for each event |know how many guests there are in each event in order to know which event I should focus more on -. 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. -**** +|`* * *` |event planner specialising in concerts |be able to filter my guests to see who have not paid for the event |easily see who I need to remind -[discrete] -==== `Ui` component +|`* * *` |event planner |be able to view all the important details of guests |get all the necessary details at one go for easier planning -*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. +|`* * *` |event planner specialising in concerts and arts festivals |be able to send the guests their tickets via email |ensure that all guests will have their tickets with them and there will be no complications -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +|`* * *` |event planner for a large event |be able to add large numbers of guests to the guest list efficiently |reduce time spent on adding them one at a time. -. 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. -**** - -. 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** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* 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. -**** - -. 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** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* 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. -**** +|`* * *` |event planner for an event with a few organisers |be able to share the guest list for an event with my fellow organizers easily |I can inform them of any changes that I have made -[discrete] -==== `Storage` component +|`* *` |event planner |filter my guests based on dietary requirements |so that I can plan my event accordingly -*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. +|`* *` |event planner specialising in conferences and recruitment talks |be able to specify the dress code of the event |ensure that the guests will be appropriately attired -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +|`* *` |event planner specialising in government and official conferences |be able to know who the VIP guests are and how many of them there are |make appropriate accommodation for them -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +|`*` |event planner |have the tickets to contain a QR code instead of using the guest’s phone number |scan them using a smart phone or any other phone with scanning capability +|======================================================================= -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +//_{More to be added}_ -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. +[appendix] +== Use Cases -*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. +(For all use cases below, the *System* is `Invités` and the *Actor* is the `user`, unless specified otherwise) -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +=== Use case: Import guest data and update payment status +Actor: Application User - Event Planner -Examples: +*MSS* -* `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. +1. User opens application and either imports csv file or adds each guest in the application. +2. System asks user to enter a command. +3. User enters a command to mark those who have paid. +4. System updates the file accordingly. -==== Step-by-step Instructions +Use case ends. -===== [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. +=== Use case: Input event details and edit venue details +Actor: Application User - Event Planner -**Main:** +*MSS* -. 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`. +1. User opens application. +2. System asks user to enter a command. +3. User enters a command to enter event details. +4. System shows the specified event name, date, venue, start time and tags. +5. User enters a command to edit venue field. +6. System shows the event name, date , start time, tags and updated venue. -**Tests:** +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`. +=== Use case: Filter guest list based on status of payment and mass email those who have not paid +Actor: Application User - Event Planner -===== [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.` +*MSS* -**Main:** +1. User opens application. +2. System asks user to enter a command. +3. User enters a command to filter out those who have yet to pay. +4. System shows an indexed list of these guests with their names, phone numbers, email address, payment status, + attendance status and tags specified, if there are people in that category. +5. User enters a command to email all in the currently displayed list, to remind them to make the payment. +6. System sends all guests in the "not paid" list an email to remind them. -. 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`. +Use case ends. -**Tests:** +=== Use case: Filter based on specific requirements and mass email all guests +Actor: Application User - Event Planner -. 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. +*MSS* -===== [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. +1. User opens application and either imports csv file or adds each guest in the application. +2. System asks user to enter a command. +3. User enters a command to filter guests based on a requirement specified (e.g. dietary requirement). +4. System displays list of all such guests, displaying their name, phone number, email address, payment status, + attendance status and tags, if there are people in that category. +5. User then enters command to list all guests. +6. System displays everyone on the guest list along with their name, phone number, email address, payment status, + attendance status and tags. +7. User enters command to remind all guests about the event. +8. System sends all guests an email reminding them about the event. -**Main:** +Use case ends. -. 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. +=== Use case: Review guest details while organising the event +Actor: Application User - Event Planner -**Tests:** +*MSS* -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +1. User opens application and either imports csv file or adds each guest in the application. +2. System will display the list of guests with details of each guest, such as name, phone number, email address, payment + status, attendance status and tags, such as, dietary requirements, VIP, etc in a row for ease of access. System will + display the general information of the event on the left of the list of guests, such as name, date, time and venue of + event, dress code, number of people attending the event so far, etc. + System asks user to enter a command. +3. User enters command to filter by some specific requirement, so that user is able to make arrangements accordingly. +4. System lists all guests with the specified requirement, if available. -===== [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. +Use case ends. -**Main:** +=== Use case: Sending tickets to individual guests +Actor: Application User - Event Planner -. 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`. +*MSS* -**Tests:** +1. User opens application and either imports csv file or adds each guest in the application. +2. User keys in email command to send an email to an individual guest +3. System will generate a QR code, which has the guest's unique ID encoded within it +4. System will email the guest their ticket, as well as details of the event. -. Add test for `Remark`, to test the `Remark#equals()` method. +Use case ends. -===== [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`]. +=== Use case: Providing smooth registration on the day of the event +Actor: Application User - Event Planner -**Main:** +*MSS* -. 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.) +1. User opens application and imports csv file(if they were not using the application while planning) or + continues with the list on the application. +2. System asks user to enter a command. +3. User keys in command to filter the list for attendees whom are absent from the event. +4. User (manning the reception/registration desk) manually keys in the guest’s unique ID found on the ticket. +5. System runs a search to match the unique ID with those in the file. +6. If unique ID is found, attendance of that guest is marked. +7. System removes all ‘marked’ guests from display and displays only those who have yet to arrive/register. +8. User can enter a command to send an email to all in the currently displayed list (comprising of guests + who have not arrived or registered yet). +9. System sends an email to each of those guests. -===== [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. +Use case ends. -**Main:** +*Extensions* + +* 6a. User enters command to unmark a guest who was marked as present accidentally. +* 6b. System unmarks the guest. -. Add a new Xml field for `Remark`. +Use case resumes from step 7. -**Tests:** +=== Use case: Importing large number of guests into the guest list of an event +Actor: Application User - Event Planner +Guarantees: Import will not result in the overwriting or deletion of an existing guest. -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +*MSS* -===== [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`]. +1. User opens application. +2. System asks user to enter a command. +3. User keys in import command along with the file path of the csv file. +4. System parses the csv file and add guests into the guest list one at a time. +6. System shows CSV entries of guests which failed to be imported along with their associated error messages + +Use case ends. -**Tests:** +*Extensions* -. 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`]. +* 3a. User keys in an invalid file path. +** 3a1. System shows an error message. -===== [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. +Use case resumes at step 2 -**Main:** +* 4a. User provided malformed CSV file or inappropriate guest fields (eg. email with no @ character). +** 4a1. System skips the addition of the guest into the guest list and saves it. -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +Use case resumes at step 4 -**Tests:** +* 5a. User provided CSV file with a guest that already exists in the current guest list. +** 5a1. System skips the addition of the guest into the guest list and saves it. -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +Use case resumes at step 4 -===== [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. +=== Use case: Export guest list of an event (to CSV file format) +Actor: Application User - Event Planner -**Main:** +*MSS* -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +1. User opens application +2. System asks user to enter a command +3. User keys in export command along with the filename of the csv file +4. System formats and saves guests into CSV format -**Tests:** +Use case ends. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +*Extensions* -==== Full Solution +* 3a. User keys in an invalid filename or a filename that already exists +** 3a1. System shows an error message. -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +Use case resumes at step 2 [appendix] -== Product Scope +== Non Functional Requirements -*Target user profile*: +. Should work on any <> as long as it has Java `9` or higher installed. +. Should be able to hold up to 1000 guests 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. +. Command line interface has to be the primary source of input. GUI is to be used only to give visual feedback to the user. +. Data should be stored locally in a text file that can be edited by user. Database Management System (DBMS) + must not be used to store data. +. OOP has to be followed. +. The software has to be independent of platforms of any kind. +. The software should work without needing an installer. +. Only free, open-source, permissive license software that do not require any installation and do not violate any + other constraints can be used. -* 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 +[appendix] +== Glossary -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +[[mainstream-os]] Mainstream OS:: +Windows, Linux, Unix, OS-X + +[[private-contact-detail]] Private contact detail:: +A contact detail that is not meant to be shared with others [appendix] -== User Stories +== Instructions for Manual Testing -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +Given below are instructions to test the app manually. -[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 +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -|`* * *` |user |add a new person | +=== Launch and Shutdown -|`* * *` |user |delete a person |remove entries that I no longer need +. Initial launch -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +.. Download the jar file and copy into an empty folder +.. Double-click the jar file + + Expected: Shows the GUI with a set of sample guest information. The window size may not be optimal. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +. Saving window preferences -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +.. Resize the window to an optimal size. Move the window to a different location. Close the window. +.. Re-launch the app by double-clicking the jar file. + + Expected: The most recent window size and location is retained. -_{More to be added}_ +//_{ more test cases ... }_ -[appendix] -== Use Cases +// tag::filterappendix[] +=== Filtering a guest -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +. Filtering a guest -[discrete] -=== Use case: Delete person +.. Test case: `filter a/absent` + + Expected: Details of guests who are absent (i.e. are labelled as "absent" in the + Attendance field) will be listed. + The number of guests listed will be shown in the status message. +.. Test case: `filter a/absent pa/paid t/Vegetarian` + + Expected: Details of guests who are absent (i.e. are labelled as "absent" in the + Attendance field), have paid (i.e. are labelled as "paid" in the + Payment field) *and* have the "Vegetarian" tag will be listed. + The number of guests who are listed will be shown in the status message. +.. Test case: `filter pa/paying` + + Expected: No guest is listed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect filter commands to try: `filter`, + `filter prefix/` (where the prefix is any other character besides 'pa', 'a' and 't') , + `filter prefix` (where prefix given does not have '/' or has any other special character), etc. + +// _{give more}_ + Expected: Similar to previous. +// end::filterappendix[] -*MSS* +// tag::findappendix[] +=== Finding a guest -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person -+ -Use case ends. +. Finding a guest -*Extensions* +.. Test case: `find n/john` + + Expected: Details of guests who have 'john' in their names. + The number of guests listed will be shown in the status message. +.. Test case: `find e/(Non-matching keyword)` + + Expected: No guest is listed. Status bar remains the same. +.. Other incorrect find commands to try: `find`, + `find prefix/` (where the prefix is any other character besides 'n', 'p' and 'e') , + `find prefix` (where prefix given does not have '/' or has any other special character), etc. + +// _{give more}_ + Expected: No guest is listed. Error details shown in the status message. + Status bar remains the same. +// end::findappendix[] -[none] -* 2a. The list is empty. -+ -Use case ends. +// tag::eventappendix[] +=== Adding event details -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. +. Adding event details -_{More to be added}_ -[appendix] -== Non Functional Requirements +.. Test case: 'add_event n/Wedding d/10/01/2019 v/XYZ Hotel st/10:00 AM'+ + Expected: Event details will be displayed in the event details panel. The number of days left to the event will be displayed in the status bar footer. -. 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. +.. Test case: 'add_event n/CFG Career Talk d/31/02/2019 v/LT 5 st/10:00 AM'+ + Expected: No event details are added. Error details are shown in the status message. Event details display remains intact. -_{More to be added}_ +.. Other incorrect commands to try: `add_event`, `add_event x`, + `add_event n/CFG Career Talk d/10/11/2019 v/LT 9 st/10' , etc. + + Expected: No event details are added. Error details are shown in the status message. Event details display remains intact. -[appendix] -== Glossary +=== Deleting event details -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +. Deleting the existing event details -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +.. Prerequisites: Event details initialised by the user must exist. -[appendix] -== Product Survey +.. Test case: `delete_event` + + Expected: Details of the event are deleted and details are not displayed in the event details panel. Status bar is updated. -*Product Name* +.. Test case: `delete_event 0` + + Expected: Event details are not deleted. Error details are shown in the status message. Status bar remains the same. -Author: ... +.. Other incorrect delete_event commands to try: `delete_event n`(where n is some parameter), etc. + + Expected: Event details are not deleted. Error details are shown in the status message. Status bar remains the same. -Pros: +=== Editing event details -* ... -* ... +. Editing the existing event details -Cons: +.. Prerequisites: Event details initialised by the user must exist. -* ... -* ... +.. Test case: 'edit_event d/10/02/2019' + + Expected: Event details will be updated in the event details panel. The number of days left to the event will be updated in the status bar footer. -[appendix] -== Instructions for Manual Testing +.. Test case: 'edit_event e/Novotel Tour Eiffel'+ + Expected: No event details are edited. Error details are shown in the status message. Event details display remains intact. -Given below are instructions to test the app manually. +.. Other incorrect commands to try: `edit_event`, `edit_event x`, + `edit_event d/31/02/2019' , etc. + + Expected: No event details are edited. Error details are shown in the status message. Event details display remains intact. +// end::eventappendix[] -[NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +// tag::emailappendix[] +=== Sending individual emails -=== Launch and Shutdown +. Send individual guests an email *with a QR code attachment* -. Initial launch +.. *Test case:* `email 1` +... Expected: Assuming there is at least one guest, UI Window opens to allow user input. If all inputs are valid, email is sent to the guest at `INDEX` 1. -.. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +.. *Incorrect commands to try:* `email The President`, `emailLL`, `email 12.3`, etc. +... Expected: No email sent due to incorrect parameters. -. Saving window preferences +=== Sending all guests an email -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. +. Send all guests an email *(no QR code attachments for this command)* + +.. *Test case:* `emailAll` +... Expected: Assuming there is at least one guest, UI Window opens to allow user input. If all inputs are valid, email is sent to all guests. + +.. *Incorrect commands to try:* `emailAll Celebrities`, `emailAllLL`, etc. +... Expected: No email sent due to incorrect parameters. + +=== Sending a group of guests an email + +. Send an email to a group of guests *(no QR code attachments for this command)* + +.. *Test case:* `emailSpecific t/Veg` +... Expected: Assuming there is at least one guest with tag `Veg` in the filtered list, UI Window opens to allow user input. If all inputs are valid, email is sent all guests with tag `Veg` + +.. *Incorrect commands to try:* `emailSpecific Puppies`, `emailSpecific t/@&@` etc. +... Expected: No email sent due to incorrect parameters. + +// end::emailappendix[] + +// tag::importexportappendix[] +=== Importing and exporting guests + +. Importing guests from CSV file + +.. Prerequisites: Delete all guests before each test case. Execute `list` followed by `clear`. + +.. Test case: Download link:{repoURL}/sample.csv[sample.csv] into directory where application Jar file is located and execute `import sample.csv` + + Expected: All guests are imported from `test.csv`. No errors are found. + +.. Test case: Download link:{repoURL}/errorsample.csv[errorsample.csv] into directory where application Jar file is located and execute `import errorsample.csv` + + Expected: No guests are imported from `test.csv`. Popup window shows all import errors. + +.. Other incorrect commands to try: `import`, `import test`, etc + + Expected: No guests are imported. Error details shown in the status message. + +. Exporting all guests + + .. Prerequisites: List all guests using `list`. There must be at least 1 guest in guest list panel after the `list` command. No `test.csv` file should be found in same directory of application Jar file. + + .. Test case: `export test.csv` + + Expected: All guests are exported into `test.csv`. `test.csv` can be found in the same directory of the application Jar file. + + .. Other incorrect commands to try: `export`, `export test`, etc + Expected: No guests are exported. Error details shown in the status message. + + +. Exporting filtered guest to CSV file + +.. Prerequisites: There must be guests that are absent. Filter all absent guests using `filter a/ABSENT`. + + +.. Test case: 'export test.csv' + + Expected: Only guests that are absent are exported into `test.csv`. `test.csv` can be found in the same directory of the application Jar file. + + +. Exporting to file that already exists + +.. Prerequisites: There must be guests in the guest list panel. Export guests using `export test.csv`. + +.. Test case: `export test.csv` + + Expected: No guests are exported. Error details shown in the status message. + + +. Exporting empty guest list + + .. Prerequisites: guest list panel in user interface must be empty. + + .. Test case: `export test.csv` + + Expected: No guests are exported. Error details shown in the status message. + +// end::importexportappendix[] + +// tag::personpreviewappendix[] +=== Guest preview + +. Displaying guest in guest preview + +.. Prerequisites: Have guests in guest list. + +.. Test case: Click on any guest in guest list panel. + + Expected: Guest details should show up in guest preview. Any command that successfully modifies the underlying guest list (use `list` to see underlying guest list) will clear the person preview. + Note: `filter` and `find` is not considered to be modifying the guest list. + +// end::personpreviewappendix[] + +// tag::generateuidappendix[] +=== Generation of UID details in add_guest command + +. Setting the UID for each guest in the guest list. + +.. Test case: `add_guest n/John p/98765432 e/f@gmail.com pa/PAID a/ABSENT u/00000 t/NORMAL t/NoShrimp t/NORMAL` + + Expected: As the field for the UID is set to `00000`, the program will generate a random 6 digit UID for the user. + +.. Test case: `add_guest n/Doe p/98725432 e/g@gmail.com pa/PAID a/ABSENT u/00001 t/NORMAL t/NoShrimp t/NORMAL` + + Expected: As the field for the UID is set to `00001`, the program will treat this is a User defined UID and add the person with the UID set to `00001`. + +.. Test case: `add_guest n/Jon p/98765422 e/d@gmail.com pa/PAID a/PRESENT u/0000 t/NORMAL t/NoShrimp t/NORMAL` + + Expected: As the field for the UID is set to `0000` which is an invalid input as the UID has to be at least 5 characters, the program will display an error message. + +.. Test case: `add_guest n/Do p/98765232 e/g@gmail.com pa/PAID a/PRESENT u/000000000000000000001 t/NORMAL t/NoShrimp t/NORMAL` + + ExpectedL As the field for the UID is set to `000000000000000000001` which has 21 characters which is an invalid input as the UID cannot be more than 20 characters, the program will display an error message. +// end::generateuidappendix[] + +// tag::attendanceappendix[] +=== Marking Attendance details + +. Marking attendance using the Unique ID (UID) to set the attendance to `PRESENT` with the `mark` command. + +.. Prerequisites: The guest list must be populated with guests. At least one guest has the UID `00001`. + +.. Test case: `mark 00001` + + Expected: The person whose UID is `00001` will have the attendance field marked as `PRESENT`. + +.. Test case: `mark [UID not in the guest list]` + + Expected: An error will show that there is no such person in the guest list. + +.. Test case: `mark 0001` + + Expected: The UID is in the wrong format and will show an error message. + +.. Test case: `mark 0101010101010101010101010101` + + Expected: the UID is in the wrong format and will show an error message. + +. Marking attendance using the Unique ID (UID) to set the attendance to `ABSENT` with the `unmark` command. + +.. Prerequisites: The guest list must be populated with guests. At least one guest has the UID `00001`. + +.. Test case: `unmark 00001` + + Expected: The person whose UID is `00001` will have the attendance field marked as `ABSENT`. + +.. Other test cases: similar to `mark` command as shown above + +// end::attendanceappendix[] + +=== Add tags to all guests in the list + +. Adds a set of tags to all the guests in the list + +.. *Test case:* `addTag t/Veg t/Platinum` +... Expected: Adds the tags `Veg` and `Platinum` to all guests in the current filtered list -_{ more test cases ... }_ +.. *Test case:* `addTag t/(*@#(*` +... Expected: No tags are added as the tags specified are not alphanumeric -=== Deleting a person +.. *Incorrect commands to try:* `addTag t/t/`, `addTag`, etc. +... Expected: No tags added due to incorrect parameters. -. Deleting a person while all persons are listed +=== Remove tags from all guests in the list -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. +. Removes a set of tags from all the guests in the list -_{ more test cases ... }_ +.. *Test case:* `removeTag t/Veg t/Platinum` +... Expected: Removes the tags `Veg` and `Platinum` from all guests who have these tags -=== Saving data +.. *Test case:* `removeTag t/Veg t/Platinum` +... Expected: If no guests in the current filtered list have tags `Veg` or `Platinum`, system throws an error -. Dealing with missing/corrupted data files +.. *Test case:* `removeTag t/(*@#(*` +... Expected: No tags are removed as the tags specified are not alphanumeric -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +.. *Incorrect commands to try:* `removeTag t/t/`, `removeTag`, etc. +... Expected: No tags removed due to incorrect parameters. -_{ more test cases ... }_ diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index 83cda0927226..000000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,266 +0,0 @@ -= Learning Outcomes -:site-section: LearningOutcomes -:toc: macro -:toc-title: -:toclevels: 1 -:sectnums: -:sectnumlevels: 1 -:imagesDir: images -:stylesDir: stylesheets -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -''' - -== Use High-Level Designs `[LO-HighLevelDesign]` - -Note how the <> describes the high-level design using an _Architecture Diagrams_ and high-level sequence diagrams. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/[se-edu/se-book: Design: Architecture] -* https://se-edu.github.io/se-book/design/introduction/multilevelDesign/[se-edu/se-book: Design: Introduction: Multi-Level Design] - -''' - -== Use Event-Driven Programming `[LO-EventDriven]` - -Note how the <> uses events to communicate with components without needing a direct coupling. Also note how the link:{repoURL}/src/main/java/seedu/address/commons/core/index/EventsCenter.java[`EventsCenter.java`] acts as an event dispatcher to facilitate communication between event creators and event consumers. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/architecturalStyles/eventDriven/[se-edu/se-book: Design: Architecture: Architecture Styles: Event-Driven Architectural Style] - -''' - -== Use API Design `[LO-ApiDesign]` - -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -image:LogicClassDiagram.png[width="800"] - -*Resources* - -* https://se-edu.github.io/se-book/reuse/apis/[se-edu/se-book: Implementation: Reuse: APIs] - -''' - -== Use Assertions `[LO-Assertions]` - -Note how the AddressBook app uses Java ``assert``s to verify assumptions. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/assertions/[se-edu/se-book: Implementation: Error Handling: Assertions] - -=== Exercise: Add more assertions - -* Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. - - -''' - -== Use Logging `[LO-Logging]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/logging/[se-edu/se-book: Implementation: Error Handling: Logging] - -=== Exercise: Add more logging - -Add more logging to AddressBook as you see fit. - - -''' - -== Use Defensive Coding `[LO-DefensiveCoding]` - -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/defensiveProgramming/[se-edu/se-book: Implementation: Error Handling: Defensive Programming] - -=== Exercise: identify more places for defensive coding - -Analyze the AddressBook code/design to identify, - -* where defensive coding is used -* where the code can be more defensive - -''' - -== Use Build Automation `[LO-BuildAutomation]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/what/[se-edu/se-book: Implementation: Integration: Build Automation: What] - -=== Exercise: Use gradle to run tasks - -* Use gradle to do these tasks: Run all tests in headless mode, build the jar file. - -=== Exercise: Use gradle to manage dependencies - -* Note how the build script `build.gradle` file manages third party dependencies such as ControlsFx. Update that file to manage a third-party library dependency. - - -''' - -== Use Continuous Integration `[LO-ContinuousIntegration]` - -Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/continuousIntegrationDeployment/[se-edu/se-book: Implementation: Integration: Build Automation: CI & CD] - -=== Exercise: Use Travis in your own project - -* Set up Travis to perform CI on your own fork. - - -''' - -== Use Code Coverage `[LO-CodeCoverage]` - -Note how our CI server <>. (https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]]) After <> for your project, you can visit Coveralls website to find details about the coverage of code pushed to your repo. https://coveralls.io/github/se-edu/addressbook-level4?branch=master[Here] is an example. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testCoverage/[se-edu/se-book: QA: Testing: Test Coverage] - -=== Exercise: Use the IDE to measure coverage locally - -* Use the IDE to measure code coverage of your tests. - -''' - -== Apply Test Case Design Heuristics `[LO-TestCaseDesignHeuristics]` - -The link:{repoURL}/src/test/java/seedu/address/commons/util/StringUtilTest.java[`StringUtilTest.java`] -class gives some examples of how to use _Equivalence Partitions_, _Boundary Value Analysis_, and _Test Input Combination Heuristics_ to improve the efficiency and effectiveness of test cases testing the link:../src/main/java/seedu/address/commons/util/StringUtil.java[`StringUtil.java`] class. - -*Resources* - -* https://se-edu.github.io/se-book/testCaseDesign/[se-edu/se-book: QA: Test Case Design] - -=== Exercise: Apply Test Case Design Heuristics to other places - -* Use the test case design heuristics mentioned above to improve test cases in other places. - -''' - -== Write Integration Tests `[LO-IntegrationTests]` - -Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. - -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. - -Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. - -[source,java] ----- -@Test -public void execute_invalidCommandFormat_throwsParseException() { - ... - assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); - assertHistoryCorrect(invalidCommand); -} ----- - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write unit and integration tests for the same method. - -* Write a unit test for a high-level method somewhere in the code base (or a new method you wrote). -* Write an integration test for the same method. - -''' - -== Write System Tests `[LO-SystemTesting]` - -Note how tests below `src/test/java/systemtests` package (e.g link:{repoURL}/src/test/java/systemtests/AddCommandSystemTest.java[`AddCommandSystemTest.java`]) are system tests because they test the entire system end-to-end. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write more system tests - -* Write system tests for the new features you add. - -''' - -== Automate GUI Testing `[LO-AutomateGuiTesting]` - -Note how this project uses TextFX library to automate GUI testing, including <>. - -=== Exercise: Write more automated GUI tests - -* Covered by `[LO-SystemTesting]` - -''' - -== Apply Design Patterns `[LO-DesignPatterns]` - -Here are some example design patterns used in the code base. - -* *Singleton Pattern* : link:{repoURL}/src/main/java/seedu/address/commons/core/EventsCenter.java[`EventsCenter.java`] is Singleton class. Its single instance can be accessed using the `EventsCenter.getInstance()` method. -* *Facade Pattern* : link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager.java`] is not only shielding the internals of the Storage component from outsiders, it is mostly redirecting method calls to its internal components (i.e. minimal logic in the class itself). Therefore, `StorageManager` can be considered a Facade class. -* *Command Pattern* : The link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command.java`] and its sub classes implement the Command Pattern. -* *Observer Pattern* : The <> used by this code base employs the Observer pattern. For example, objects that are interested in events need to have the `@Subscribe` annotation in the class (this is similar to implementing an `\<>` interface) and register with the `EventsCenter`. When something noteworthy happens, an event is raised and the `EventsCenter` notifies all relevant subscribers. Unlike in the Observer pattern in which the `\<>` class is notifying all `\<>` objects, here the `\<>` classes simply raises an event and the `EventsCenter` takes care of the notifications. -* *MVC Pattern* : -** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. -** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). -* *Abstraction Occurrence Pattern* : Not currently used in the app. - -*Resources* - -* https://se-edu.github.io/se-book/designPatterns/[se-edu/se-book: Design: Design Patterns] - -=== Exercise: Discover other possible applications of the patterns - -* Find other possible applications of the patterns to improve the current design. e.g. where else in the design can you apply the Singleton pattern? -* Discuss pros and cons of applying the pattern in each of the situations you found in the previous step. - -=== Exercise: Find more applicable patterns - -* Learn other _Gang of Four_ Design patterns to see if they are applicable to the app. - -''' - -== Use Static Analysis `[LO-StaticAnalysis]` - -Note how this project uses the http://checkstyle.sourceforge.net/[CheckStyle] static analysis tool to confirm compliance with the coding standard. - -*Resources* - -* https://se-edu.github.io/se-book/qualityAssurance/staticAnalysis/[se-edu/se-book: QA: Static Analysis] - -=== Exercise: Use CheckStyle locally to check style compliance - -* Install the CheckStyle plugin for your IDE and use it to check compliance of your code with our style rules (given in `/config/checkstyle/checkstyle.xml`). - -''' - -== Do Code Reviews `[LO-CodeReview]` - -* Note how some PRs in this project have been reviewed by other developers. Here is an https://github.com/se-edu/addressbook-level4/pull/147[example]. -* Also note how we have used https://www.codacy.com[Codacy] to do automate some part of the code review workload (https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]]) - - -=== Exercise: Review a PR - -* Review PRs created by team members. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..8e155b771301 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += Invités - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,33 +12,55 @@ 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-F09-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team F09-3` Since: `Sep 2018` Licence: `MIT` == Introduction +Invités is a desktop application targeted at event managers and planners. The application allows you to efficiently *organise, cater, and manage* large events such +as weddings, school gatherings, orientation camps, etc. The application works on a command line interface, where you can key in the commands specified in our User Guide into a command box to perform a task. +Some of the main distinguishing features of the application include the ability to send mass emails to guests, keep track of payments, and take note of guest attendance. +The application also allows you to input the proposed or confirmed details of your event and provides a countdown to the event date. +To add to these features, by employing a standardised format, the application is able to take in Comma Separated Values (CSV) files and import the guest list for a particular event. +This eliminates the need to key in the guest details manually and gives you an alternative if you decide to organise another event using the same guest list. -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! +You will have an easier time managing the reception of your event as you will be equipped with tools that will minimise errors in catering, +organising, and ordering. Moreover, our application aims to reduce your frustration while planning events by packaging your needs in a platform that is more user-friendly, personalised, and efficient. + +The main purpose of this User Guide is to give you a detailed overview of all the features in our application and how to use them. == 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. -. 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: +. Download the latest jar file from the GIT repository link:https://github.com/CS2113-AY1819S1-F09-3/main/releases[here]. +. Place the jar file where your home directory resides +. Double click on the jar file and wait a couple of seconds as the application loads. If you're successful, a main screen will load + similar to the one shown below: + + +**** +|==== +| image:Ui.png[width="790"] + + + Figure 1 - Invités: An event management system. +|==== +**** -* *`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 +. As the Command Line Input is your primary method of manipulation, here are some + example commands that you can use: -. Refer to <> for details of each command. +* `help` : Displays a summary of the list of commands that the application offers. +* `add_guest` : Adds a guest to the guest list. +* `delete_guest` : Deletes an entry from the guest list. +* `mark` : Marks the attendance of a guest from `ABSENT` to `PRESENT`. +* `exit` : exits the application. + +. You should 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. + +. Please refer to <> subsection to see more detailed documentation of the functions that + are included in this application +. If you would like to perform emailing services through our application, please complete the steps as listed + in <> [[Features]] == Features @@ -46,215 +68,656 @@ 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 you e.g. in `add_guest n/NAME`, `NAME` is a parameter which can be used as `add_guest n/Bob Lee`. +* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/Bob Lee t/VIP` or as `n/Bob Lee`. +* 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/VIP`, `t/VIP t/Vegetarian` etc. +* You can specify parameters in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. ==== === Viewing help : `help` +Displays a summary of the list of commands that the application offers + Format: `help` -=== Adding a person: `add` +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `help extra_characters` or `help 182$*` will be incorrect, but `help {nbsp}` (i.e. with spaces) will be correct. -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +=== Listing all guests : `list` -[TIP] -A person can have any number of tags (including 0) +Shows a list of all guests in the guest list. + +Format: `list` + +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `list extra_characters` or `list 182$*` will be incorrect, but `list {nbsp}` (i.e. with spaces) will be correct. + +// @@author Sarah + +// tag::find[] +=== Locating guests by name, phone number or email address: `find` + +Find guests whose names, phone numbers and/or email addresses +contain any of the given keywords. + +Format: `find n/KEYWORD p/MORE_KEYWORDS e/MORE_KEYWORDS` + +Example: find n/NAME p/PHONE e/EMAIL + +**** +* The search is case-insensitive. e.g `n/hans` will match `n/Hans` +* The order of the keywords does not matter. e.g. `n/Hans n/Bo` will match `n/Bo n/Hans` +* Only names, phone numbers and email addresses are searched, depending on prefixes given. +* Only full words will be matched e.g. `n/Han` will not match `n/Hans` +* Guests matching at least one keyword will be returned (i.e. `OR` search). e.g. `n/Hans n/Bo` will return `Hans Gruber`, `Bo Yang` +**** 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` +* `find n/John` + +You will be shown a list that contains the entries of `john` and `John Doe` +* `find n/Betsy n/Tim n/John` + +You will be show a list containing entries of any guest having names `Betsy`, `Tim`, or `John` +* `find n/alex p/92746838 e/johndoe@gmail.com` + +You will be show a list containing entries of any guest having the name `Alex`, +phone number `92746838`, or email address `johndoe@gmail.com` +* `find n/david n/edan` + +You will be shown a list that contains the entries of any guests having the +name `david` and `edan` + +* `find n/david edan` + +You will be shown a list that contains the entries of any guests having the +name `david` *but not* `edan` + +// end::find[] +// @@author + +=== Adding a guest's details: `add_guest` + +Adds a guest to the guest list. + +No spaces or special characters allowed in Payment and Attendance. + +Payment accepts "PAID", "NOTPAID" , "PENDING" or "N.A.". +Attendance accepts "ABSENT", "PRESENT" or "N.A." + +Payment and attendance are case-insensitive. + +Unique ID (UID) accepts a minimum of 5 characters and a maximum of 20 characters + +If any options other than the ones given are entered, the guest will be added if +other fields are fine, but payment and/or attendance will be blank. + +Format: `add_guest n/NAME p/PHONE_NUMBER e/EMAIL pa/PAYMENT a/ATTENDANCE u/UID [t/TAG]...` -=== Listing all persons : `list` +[TIP] +A guest can have any number of tags (including 0) +UID can be generated by the program by entering `u/00000`, + +Other values of UID will be treated as a user-defined UID. -Shows a list of all persons in the address book. + -Format: `list` +Examples: -=== Editing a person : `edit` +* `add_guest n/Bob Lee p/81720172 e/boblee@gmail.com a/Absent pa/NOTPAID u/00001 t/VIP t/Vegetarian` +* `add_guest n/John Doe p/91028392 e/johndoe@gmail.com a/Present pa/PAID u/00002 t/Groom t/NonVegetarian` +* `add_guest n/Carl Sagan p/85174321 e/carlsagan@gmail.com a/Absent pa/PAID u/00000 t/VIP` (will trigger randomly generated UID) +* `add_guest n/David Li p/83186624 e/davidli@gmail.com pa/PENDING a/ABSENT u/00003` (will be treated as a User defined UID) -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +=== Editing a guest's details: `edit_guest` + +Edits an existing guest entry in the guest list. + +No spaces or special characters allowed in Payment and Attendance. + +Payment accepts "PAID", "NOTPAID" , "PENDING" or "N.A.". + +Attendance accepts "ABSENT", "PRESENT" or "N.A." + +Payment and attendance are case-insensitive. + +Format: `edit_guest INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [pa/PAYMENT] [a/ATTENDANCE] + [t/GUEST_TYPE] [t/DIET]...` + +[WARNING] +Unique ID (UID) cannot be changed by edit_guest. **** -* 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 guest at the specified `INDEX`. The index refers to the index number shown in the displayed guest list. The index *must be a positive integer* 1, 2, 3, ... * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags, the existing tags of the guest will be removed i.e adding of tags is not cumulative. +* You can remove all of the guest'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_guest 2 n/Bob Chan` + +Edits the name of the 2nd guest to be `Bob Chan`. -=== Locating persons by name: `find` +=== Deleting a guest's details: `delete_guest` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Deletes the specified guest from the guest list. + +Format: `delete_guest 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 guest at the specified `INDEX`. +* The index refers to the index number shown in the displayed guest list. +* The index *must be a positive integer* 1, 2, 3, *and* cannot be out of bounds. For example, if there are 4 guests in your application, +the command `delete_guest 5` will give you an error as there does not exist a guest at `INDEX` 5. **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `list` + +`delete_guest 2` + +Deletes the 2nd guest in the guest list. +* `find n/Betsy` + +`delete_guest 1` + +Deletes the 1st guest in the results of the `find` command. -=== Deleting a person : `delete` +// @@author aaryamNUS +// tag::addremovetag[] +=== Adding a set of tags to all guests : `addTag` -Deletes the specified person from the address book. + -Format: `delete INDEX` +This command allows you to add a set of tags to all guests in the current filtered guest list. + +Format: `addTag [t/TAG]...` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... +* The addTag command will add any number of tags provided by you to all guests. +* You must provide tags that are alphanumeric, otherwise the system will give you an error. +* Moreover, if you provide empty tags, or tags that all your guests already have, the system will throw an error. **** 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. +* `addTag t/Veg t/VIP` + +You will add the tags `Veg` and `VIP` to all guests in the current filtered list. +* `addTag t/@` + +This will present you with an error as all your tags must be alphanumeric. +* `addTag` + +You will receive an error message as you have provided empty tags. -=== Selecting a person : `select` +=== Removing a set of tags from all guests : `removeTag` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +This command allows you to remove a set of tags from all guests in the current filtered guest list. + +Format: `removeTag [t/TAG]...` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* `1, 2, 3, ...` +* The removeTag command will remove any number of tags provided by you from all guests. +* If the tags you provided are not shared by any of the guests in the current list, the system with throw an error. +* You must provide *non-empty* tags, e.g. an input of _removeTag_ will not do anything. +* You must provide tags that are alphanumeric, otherwise the system will throw an error. **** 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. +* `removeTag t/Veg t/VIP` + +You will remove the tags `Veg` and `VIP` from all guests in the current filtered list. +* `removeTag` + +This will present you with an error, as you have not provided any tags to delete. +* `removeTag t/@!*` + +This will present you with an error, as all your tags must be alphanumeric. + +// end::addremovetag[] +// @@author -=== Listing entered commands : `history` +// @@author wm28 +// tag::import[] +=== Importing guests from CSV file : `import` + +Import multiple guests with data from a specified CSV file. To create your own CSV file for importing guests, please see <> for the accepted format. + + +Format: `import FILE_PATH` + +**** +* This command only adds guests into the current guest list. No existing guest in the guest list will be deleted due to the import. +* Adds all guests specified in the CSV file specified by `FILE_PATH` +* `FILE_PATH` shall only be a relative or an absolute file path. +** Relative file path is relative to where the application Jar file is located. +* There is no guaranteed ordering of guests after each import. +* Importation of guests which already exist will be skipped. +** A guest will be classified as an existing guest if it has the same name and matching phone number or email address with an existing guest in the guest list. +* Importation of badly formatted guests in the CSV file will be skipped. +* The CSV guest entries which are badly formatted or those which corresponds to an existing guest in the guest list, will trigger an import report window as shown in figure 2. + +|==== +| image:ImportReportWindow.PNG[width="790"] + + + Figure 2 - Import Report Window: Shows the offending CSV guest entries with their associated error messages. +|==== +**** -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Examples: + +* `import directory/subdirectory/guestlist.csv` + +You will populate the guest list with the data imported from the CSV file in the specified path. + +// end::import[] + +// tag::export[] + +=== Exporting guests to CSV file : `export` + +Exports guests' data in the guest list to a specified CSV file. Allows you to share your guest list easily using the exported CSV file. The format of guest fields in the CSV file is the same format as the `import` command and can be found in <>. + + +Format: `export FILE_PATH` + +**** +* Command will only export the guests that you see in the guest list panel of the user interface. +** To export selected guests, use the `filter` command before exporting. +* `FILE_PATH` shall only be a relative or an absolute file path. +** Relative file path is relative to where the application Jar file is located. +**** + +Examples: + +* `export directory/subdirectory/guestlist.csv` + +You will export the currently filtered guest list entries into a CSV file in the specified path. + +// end::export[] +// @@author + +// @@author kronicler +// tag::mark[] +=== Marking a guest as present : `mark` + +Marks a guest as present using a unique ID (UID) that was assigned to them upon adding them into the guest list. +This will also update the attendance​ field associated with the guest to `PRESENT`. + +Format: `mark [UID]` [NOTE] -==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +`UID` does not use the u/ prefix. + +`UID` only accepts a string of numbers alphanumeric characters between 5 to 20 characters inclusive, +other characters will trigger an invalid command format error. + +Examples: + +* `mark 543654` You will set the guest with UID `543654` as `PRESENT`. +* `mark A345654M` You will set the guest with UID `A345654M` as `PRESENT`. +* `mark ALPHA` You will set the guest with UID `ALPHA` as `PRESENT`. + +image::markCommandSuccess.PNG[width="790"] +Figure 3 - User interface after Mark Command : Successful execution of `MarkCommand` + +// end::mark[] + +// tag::unmark[] +=== Marking a guest as absent : `unmark` + +Marks a guest as absent using a unique ID (UID) that was assigned to them upon adding them into the guest list. +This will also update the attendance​ field associated with the guest to `ABSENT`. + +Format: `mark [UID]` + +[NOTE] +`UID` does not use the u/ prefix. + +`UID` only accepts a string of numbers alphanumeric characters between 5 to 20 characters inclusive, +other characters will trigger an invalid command format error. + +Examples: + +* `unmark 543654` You will set the guest with UID `543654` as `ABSENT`. +* `mark A345654M` You will set the guest with UID `A345654M` as `ABSENT`. +* `mark ALPHA` You will set the guest with UID `ALPHA` as `ABSENT`. + +image::unmarkCommandSuccess.png[width="790"] +Figure 4 - User interface after Unmark Command : Successful execution of `UnmarkCommand` + +// end::unmark[] +// @@author + +// tag::start_marking[] +=== Starting mass attendance marking : `start_marking` - _Coming in v2.0_ + +[NOTE] +This feature has not been implemented yet. Our team plans to implement this feature in an upcoming version, v2.0. + +Start the mass attendance marking mode. Allows you to mark attendance without using +the mark prefix. This command will allow you to continuously mark the attendance of the attendees by keying in their Unique ID (UID) +into the command line interface +Format: `start_marking` + `[UID]...` + +[NOTE] +You are unable to use other commands once this mode is active. + +You will need to use the `stop_marking` command to deactivate this mode to use other commands + +Examples: + +* `start_marking` + `00001` + `708944` + `928372` + `00003...` + +You will mark the guests with UID of `00001`, `708944`, `928372`, `00003` as `PRESENT`. +// end::start_marking[] + +// tag::stop_marking[] +=== Stopping mass attendance marking : `stop_marking` - _Coming in v2.0_ + +[NOTE] +This feature has not been implemented yet. Our team plans to implement this feature in an upcoming version, v2.0. + +Stops the mass attendance marking mode. Deactivates the `start_marking` mode to enable usage of other commands in the program + +Format: `stop_marking` + +Examples: + +* `start_marking` + `00001` + `stop_marking` + +Initiate `start_marking` mark the person with UID `00001` then exit the mode with `stop_marking`. +// end::stop_marking[] + +// @@author Sarah +// tag::filter[] +=== Filtering the guest list based on specified parameters : `filter` + +Filters the current guest list based on the specified filter parameters. Only filters based on +payment status, attendance status and tags. Keywords should not have spaces or any +special characters. + +Values accepted for Payment Status: PAID, NOTPAID, PENDING or N.A. + +Values accepted for Attendance Status: PRESENT, ABSENT or N.A. + +Format: `filter [pa/PAYMENT_STATUS] [a/ATTENDANCE_STATUS] + [t/DIET] [t/GUEST_TYPE] [t/...]` + +**** +* The search is case-insensitive. e.g `pa/paid` will match `pa/PAID`. +* The order of the keywords does not matter. e.g. `pa/PAID a/ABSENT` will match `a/ABSENT pa/PAID`. +* Only payment status, attendance status and tags are searched, depending on prefixes given. +* Only full words will be matched. e.g. `pa/PAID` will not match `pa/NOTPAID`. +* Guests matching all keywords will be returned (i.e. `AND` search). e.g. `pa/PAID t/GUEST` will return a list +of people who have paid *and* are guests. +**** + +Examples: + +* `filter pa/NOTPAID a/PRESENT` + +You will be shown a list with guests who have yet to pay and are present at your event. + +* `filter a/Present t/Vegetarian` + +You will be shown a list with guests who are present and have a vegetarian dietary requirement. +// end::filter[] +// @@author + +// @@author aaryamNUS +// tag::email[] +=== Sending emails to individual guests : `email` + +|=== +|_Please ensure you have gone through <> first in order for this feature to work!_ +|=== + +With this command you can send an email, *with a QR code attachment*, to the guest at a specific Index + +Format: `email INDEX` + +**** +* Sends an email to the guest at the specified `INDEX`. +* The index refers to the index number of the guest as shown in the displayed guest list. +* The index *must be a positive integer* 1, 2, 3, *and* cannot be out of bounds. For example, if there are 4 guests in your application, +the command `email 5` will give you an error as a guest does not exist at `INDEX` 5. +**** + +*Examples*: + +* `email 2` + +1. First, you will be presented with an EmailWindow similar to *Figure 5* below. +2. This window is for you to input your email address, password, email subject and message. +3. You will then need to fill in all the required fields. If you miss any of the fields and try to click the `Send` button, an error message will pop up like the one in *Figure 6*. +4. Once all fields are filled, you can click the `Send` button to send your email to the *2nd guest* in the list. You can also click the `Quit` button if you do not want to send your email. +5. If you are successful, you will see a message that says `Successfully sent email!`. + +[cols="2*"] +|==== +| image:EmailWindow.png[] Figure 5 - EmailWindow +| image:EmailWindowMissingMessage.png[] Figure 6 - Missing email message +|==== + +=== Sending an email to all guests : `emailAll` +|=== +|_Please ensure you have gone through <> first in order for this feature to work!_ +|=== + +With this command you can an email *(no QR code attachments for this command)* to *all of the guests* in the current filtered list + +Format: `emailAll` + +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `emailAll extra_characters` or `emailAll 182$*` will be incorrect, but `emailAll {nbsp}` (i.e. with spaces) will be correct. + +Examples: + +* `list` + +* `emailAll` + +1. The command `list` will list all your guests to ensure you send an email to all guests. +2. First, you will be presented with an EmailWindow similar to *Figure 5* above. +3. This window is for you to input your email address, password, email subject and message. +4. You will then need to fill in all the required fields. If you miss any of the fields and try click the `Send` button, an error message will pop up as the one in *Figure 6* above. +5. Once all fields are filled, you can click the `Send` button to send your email to all guests in the list. You can also click the `Quit` button if you do not want to send your email. +6. If you are successful, you will see a message that says `Successfully sent email!`. + +=== Sending emails to specific groups of guests : `emailSpecific` +|=== +|_Please ensure you have gone through <> first in order for this feature to work!_ +|=== + +With this command you can send an email *(no QR code attachments for this command)* to all the guests with *at least one of the tags specified*+ +Format: `emailSpecific [t/TAG]...` + +**** +* If the tags you provided are not shared by any of the guests in the current list, the system will throw an error +* You must provide *non-empty* tags; an input of _emailSpecific_ will not do anything. +* You must provide tags that are alphanumeric, otherwise the system will throw an error. +**** + +Examples: + +* `emailSpecific` + +This will present you with an error, as you have not provided any tags to delete +* `emailSpecific t/@!*` + +This will present you with an error, as all your tags must be alphanumeric +* `emailSpecific t/VIP t/Garbage` + +1. This command will send an email to all the guests with a `VIP` tag, assuming no guest has the tag `Garbage`! (You can provide such `Garbage` tags!) +2. First, you will be presented with an EmailWindow similar to *Figure 5* above. +3. This window is for you to input your email address, password, email subject and message. +4. You will then need to fill in all the required fields. If you miss any of the fields and try click the `Send` button, an error message will pop up as the one in *Figure 6* above. +5. Once all fields are filled, you can click the `Send` button to send your email to all guests in the list. You can also click the `Quit` button if you do not want to send your email. +6. If you are successful, you will see a message that says `Successfully sent email!`. + +// end::email[] +// @@author + +// @@author SandhyaGopakumar +// tag::event[] +=== Adding details about your event: `add_event` +Adds details such as the name, date, venue and start time of your event. Any additional details may be entered as tags. + +Format: `add_event n/EVENT_NAME d/DATE v/VENUE st/START_TIME [t/OTHER_TAGS]` + +**** +* All compulsory fields(name, date, venue and start time) must be specified. The optional field(ie, tags) may be omitted. +* Event name and venue have to be alphanumeric and may contain spaces. Otherwise, the system will inform you about the correct format to be followed. Special characters like '#', ',' and '-' may be used for the venue field. +* Event date has to follow the 'dd/mm/yyyy' format and has to exist in the calendar. Ensure that the event date falls after the current system date. Otherwise, the system will inform you about the invalid date. +* Event's start time should follow the 'h:mm AM/PM' format with h between 1 to 12 and mm between 00 to 59. Otherwise, the system will inform you about the correct format to be followed. +* Event tags must be alphanumeric. Spaces are not allowed +**** + +Examples: + +* `add_event n/CFG career talk d/12/01/2019 v/YIH Paris Room st/9:00 AM t/SmartCasualAttire` + +The event details panel will show you an event called `CFG career talk` that will take place on 12th January, 2019 at YIH Paris Room. The event will start at 9:00 AM and attendees are expected to dress in smart casual attire. + +=== Editing your event's details : `edit_event` + +Edits the details of your event. + +Format: `edit_event [n/EVENT_NAME] [d/DATE] [v/VENUE] [st/START_TIME] [t/...]` + +**** +* Ensure that you have specified some event details before using this command. Otherwise, the system will inform you about the lack of event details. +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing tags, the existing tags of the event will be removed i.e adding of tags is not cumulative. +* You can remove all event tags by typing `t/` without specifying any tags after it. +**** + +Examples: + +* `edit_event n/CFG Career Workshop t/CasualAttire` + +You will change the name of your event to 'CFG Career Workshop and replace the existing tags with the 'CasualAttire' tag. + +=== Deleting your event's details : `delete_event` + +Deletes the event details currently present in the application. + +Format: `delete_event` + +**** +* Ensure that you have specified some details before using this command. Otherwise, the system will inform you about the lack of event details. +* You should not enter any characters after the command word, only extra spaces are allowed. +For example, the following commands are incorrect: 'delete_event 182' or 'delete_event xyz' where 'x', 'y' and 'z' correspond to any characters except blank spaces. +**** + +Examples: + +* `delete_event` + +You will delete the event details. + +// end::event[] +// @@author // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores the guest list to the state before the previous _undoable_ command was executed. + Format: `undo` +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `undo extra_characters` or `undo 182$*` will be incorrect, but `undo {nbsp}` (i.e. with spaces) will be correct. + [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the guest list's content (`add_guest`, `delete_guest`, `edit_guest`, `removeTag`, `addTag`, `clear` and +`import`) and the event details display's content('add_event', 'edit_event' and 'delete_event'). The other commands we provide are therefore *not* undoable. ==== Examples: -* `delete 1` + +* `delete_guest 1` + `list` + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `delete_guest 1` command) + * `select 1` + `list` + `undo` + The `undo` command fails as there are no undoable commands executed previously. -* `delete 1` + +* `delete_guest 1` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `delete_guest 1` command) + === Redoing the previously undone command : `redo` Reverses the most recent `undo` command. + Format: `redo` +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `redo extra_characters` or `redo 182$*` will be incorrect, but `redo {nbsp}` (i.e. with spaces) will be correct. + Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `delete_guest 1` + +`undo` (reverses the `delete_guest 1` command) + +`redo` (reapplies the `delete_guest 1` command) + -* `delete 1` + +* `delete_guest 1` + `redo` + The `redo` command fails as there are no `undo` commands executed previously. -* `delete 1` + +* `delete_guest 1` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +`undo` (reverses the `delete_guest 1` command) + +`redo` (reapplies the `delete_guest 1` command) + `redo` (reapplies the `clear` command) + -// end::undoredo[] - -=== Clearing all entries : `clear` -Clears all entries from the address book. + -Format: `clear` +// end::undoredo[] === Exiting the program : `exit` Exits the program. + Format: `exit` -=== Saving the data - -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +[NOTE] +You should not enter any characters after the command word, only extra spaces are allowed! For example, the input `exit extra_characters` or `exit 182$*` will be incorrect, but `exit {nbsp}` (i.e. with spaces) will be correct. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +=== Saving your data -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +The guest list data are saved in the hard disk automatically after any command that changes the data. + +There is no need for you to save your data manually. == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous guest list. + +Alternatively, you can export the data from your current computer using the export command. Using this data, you can launch the application on a different computer and import the data. + +*Q*: ​How do I import my existing data on a CSV into the application? + +*A*: Firstly, create a new event within the application. After this, use the import function and specify the path to the file. +You will see the guest list populate itself with the data from the specified CSV file. + +// tag::settingupemail[] +== Enabling Email Services + +In order for you to use the commands `email`, `emailAll`, and `emailSpecific` you must allow Invités to access your email account and +send emails. Currently, our application only supports Gmail accounts, but we do plan on supporting other email domains. + +If you do have a *Gmail* account, please follow these steps to enable email services: + +1. Login to your Gmail account using your preferred online browser (e.g. Chrome, Firefox). +2. Click on your profile picture on the top right, and click on `Google Account` +3. Once you are re-directed, under the `Sign-in and security` section, click on `Apps with account access` +4. Scroll down till you find the section `Allow less secure apps` on the right. Set this option to `ON`. +5. You are now ready to send emails to your guests through Invités! + +[WARNING] +Currently there is no other way to enable mailing services than to let your Gmail account allow less secure apps. However, our team is working quickly to find a more secure replacement. + +[IMPORTANT] +If your operating system is *macOS Mojave*, please make an *important* note of the following: + +1. If you use any of the email-related commands (i.e. _email_, _emailAll_ or _emailSpecific_), you will be presented with an EmailWindow similar to +the one in *Figure 5*. + +2. If you use the *command-tab* keys *when the EmailWindow is open* to switch windows, the application will crash. This is due to +Apple's latest macOS release, in which some of the bindings are not compatible with key JavaFX functions. + +3. This issue does not occur on Windows, Linux, and older macOS systems, however it _may still exist_ in other operating systems we have not tested on. + +4. We sincerely apologise for the inconvenience caused, and our team is quickly trying to fix this issue. + +|=== +|_For *testing* purposes, you may use a default Gmail account we have created to save you some time:_ +|Email Address: _invitestestpe1@gmail.com_ +|Password: _practicalexam1_ +|=== + +// end::settingupemail[] + +// tag::csvformat[] +== CSV Guest List Format + +The `import` and `export` command will only work with CSV files satisfying a predefined format. To create valid CSV files, guests fields must be in the following format below and each guest's details must be entered on a new line. + +Format: `NAME,PHONE_NUMBER,EMAIL,PAYMENT_STATUS,ATTENDANCE,UID,[TAG]` + +[WARNING] +==== +Individual guest fields shall not contain any commas. +==== + +Example: sample CSV file +**** +David Li,91031282,\lidavid@gmail.com,PENDING,ABSENT,00001,gold,Veg,VIP + +Irfan Ibrahim,92492021,\irfan@gmail.com,PAID,PRESENT,00002,gold,Veg,VIP + +Roy Balakrishnan,92624417,\royb@gmail.com,PENDING,ABSENT,00003,gold,Veg,VIP + +**** + +// end::csvformat[] == 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` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* `help` : ​Displays a help sheet containing useful commands. +* `add_guest` : ​Creates an entry containing the details of a guest attending the event. +* `edit_guest` :​ Modifies the entry of a specified guest based on index. +* `delete_guest` : ​Removes an entry of a specified guest based on index. +* `removeTag` : Removes a set of tags from all the guests in the current list. +* `addTag` : Adds a set of tags to all the guests in the current list. +* `find` : Finds guests whose names, phone numbers or email addresses contain any of the given keywords. +* `list` : Lists the current guest list. +* `import` : ​Automatically generates and displays the guest list from a given CSV file. +* `export` : Exports the current guest list to a CSV file. +* `mark` : ​Tags a guest to note that they are currently at the event. +* `unmark` : Tags a guest to note that they are currently absent at the event​. +* `filter` : ​Filters the guest list based on keywords given. +* `email` : ​Sends individual emails to a guest based on the index specified. +* `emailAll` : Sends an email to all of the guests in the current list. +* `emailSpecific` : Sends an email to all guests with the specified tags. +* `add_event` :​ Adds the details of your event. +* `edit_event` : Edits the details of your event. +* `delete_event` : ​Removes your event details. +* `undo` : Restores the application to the state before the previous undoable command was executed. +* `redo` : Reverses the most recent undo command. +* `exit` : ​Exits the application. diff --git a/docs/diagrams/AddEventSequenceDiagram.pptx b/docs/diagrams/AddEventSequenceDiagram.pptx new file mode 100644 index 000000000000..82e016f84825 Binary files /dev/null and b/docs/diagrams/AddEventSequenceDiagram.pptx differ diff --git a/docs/diagrams/AddGuestSequenceDiagram.pptx b/docs/diagrams/AddGuestSequenceDiagram.pptx new file mode 100644 index 000000000000..b4d018b1004e Binary files /dev/null and b/docs/diagrams/AddGuestSequenceDiagram.pptx differ diff --git a/docs/diagrams/ArchitectureDiagram.pptx b/docs/diagrams/ArchitectureDiagram.pptx index b0e5a9d0ff55..34f3f5e39c84 100644 Binary files a/docs/diagrams/ArchitectureDiagram.pptx and b/docs/diagrams/ArchitectureDiagram.pptx differ diff --git a/docs/diagrams/DeleteEventSequenceDiagram.pptx b/docs/diagrams/DeleteEventSequenceDiagram.pptx new file mode 100644 index 000000000000..388a5976a4e2 Binary files /dev/null and b/docs/diagrams/DeleteEventSequenceDiagram.pptx differ diff --git a/docs/diagrams/EditEventSequenceDiagram.pptx b/docs/diagrams/EditEventSequenceDiagram.pptx new file mode 100644 index 000000000000..afd5a0173b09 Binary files /dev/null and b/docs/diagrams/EditEventSequenceDiagram.pptx differ diff --git a/docs/diagrams/FilterSequenceDiagram.pptx b/docs/diagrams/FilterSequenceDiagram.pptx new file mode 100644 index 000000000000..03aa635b148d Binary files /dev/null and b/docs/diagrams/FilterSequenceDiagram.pptx differ diff --git a/docs/diagrams/FindSequenceDiagram.pptx b/docs/diagrams/FindSequenceDiagram.pptx new file mode 100644 index 000000000000..a5630b2d3ec0 Binary files /dev/null and b/docs/diagrams/FindSequenceDiagram.pptx differ diff --git a/docs/diagrams/MarkUnmarkSequenceDiagram.pptx b/docs/diagrams/MarkUnmarkSequenceDiagram.pptx new file mode 100644 index 000000000000..5496444fbeb2 Binary files /dev/null and b/docs/diagrams/MarkUnmarkSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx index d0561dfd305a..f7f24259031e 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..65ff9ffcc63c 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..db941c3e0c9e 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..0506eb8eb7f8 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..af3a5c563b7e 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/images/AddCommandSequenceDiagram.png b/docs/images/AddCommandSequenceDiagram.png new file mode 100644 index 000000000000..89b9cd100729 Binary files /dev/null and b/docs/images/AddCommandSequenceDiagram.png differ diff --git a/docs/images/AddEventSequenceDiagram.png b/docs/images/AddEventSequenceDiagram.png new file mode 100644 index 000000000000..2ee82af17426 Binary files /dev/null and b/docs/images/AddEventSequenceDiagram.png differ diff --git a/docs/images/AddTagSequenceDiagram.png b/docs/images/AddTagSequenceDiagram.png new file mode 100644 index 000000000000..ca5114037b36 Binary files /dev/null and b/docs/images/AddTagSequenceDiagram.png differ diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index bdc789000f77..adf0e8140edd 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/DeleteEventSequenceDiagram.png b/docs/images/DeleteEventSequenceDiagram.png new file mode 100644 index 000000000000..9ad56475bb1c Binary files /dev/null and b/docs/images/DeleteEventSequenceDiagram.png differ diff --git a/docs/images/EditEventSequenceDiagram.png b/docs/images/EditEventSequenceDiagram.png new file mode 100644 index 000000000000..a5cd48d71d0f Binary files /dev/null and b/docs/images/EditEventSequenceDiagram.png differ diff --git a/docs/images/EmailAllClassDiagram.png b/docs/images/EmailAllClassDiagram.png new file mode 100644 index 000000000000..d4236cdd9156 Binary files /dev/null and b/docs/images/EmailAllClassDiagram.png differ diff --git a/docs/images/EmailSpecificClassDiagram.png b/docs/images/EmailSpecificClassDiagram.png new file mode 100644 index 000000000000..6f1e9f02f05c Binary files /dev/null and b/docs/images/EmailSpecificClassDiagram.png differ diff --git a/docs/images/EmailWindow.png b/docs/images/EmailWindow.png new file mode 100644 index 000000000000..7810ad95c306 Binary files /dev/null and b/docs/images/EmailWindow.png differ diff --git a/docs/images/EmailWindowMissingAddress.png b/docs/images/EmailWindowMissingAddress.png new file mode 100644 index 000000000000..89694c4de841 Binary files /dev/null and b/docs/images/EmailWindowMissingAddress.png differ diff --git a/docs/images/EmailWindowMissingMessage.png b/docs/images/EmailWindowMissingMessage.png new file mode 100644 index 000000000000..fba040536dc7 Binary files /dev/null and b/docs/images/EmailWindowMissingMessage.png differ diff --git a/docs/images/EmailWindowMissingSubject.png b/docs/images/EmailWindowMissingSubject.png new file mode 100644 index 000000000000..6712976cdc78 Binary files /dev/null and b/docs/images/EmailWindowMissingSubject.png differ diff --git a/docs/images/ExportCommandSequenceDiagram.PNG b/docs/images/ExportCommandSequenceDiagram.PNG new file mode 100644 index 000000000000..fa20653fcac9 Binary files /dev/null and b/docs/images/ExportCommandSequenceDiagram.PNG differ diff --git a/docs/images/FilterSequenceDiagram.png b/docs/images/FilterSequenceDiagram.png new file mode 100644 index 000000000000..af0596b0cd69 Binary files /dev/null and b/docs/images/FilterSequenceDiagram.png differ diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 000000000000..1ed9f79f19de Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ diff --git a/docs/images/ImportCommandSequenceDiagram.PNG b/docs/images/ImportCommandSequenceDiagram.PNG new file mode 100644 index 000000000000..9892b9108a70 Binary files /dev/null and b/docs/images/ImportCommandSequenceDiagram.PNG differ diff --git a/docs/images/ImportExportClassDiagram.PNG b/docs/images/ImportExportClassDiagram.PNG new file mode 100644 index 000000000000..029b9cb344ec Binary files /dev/null and b/docs/images/ImportExportClassDiagram.PNG differ diff --git a/docs/images/ImportReportWindow.PNG b/docs/images/ImportReportWindow.PNG new file mode 100644 index 000000000000..2c6e513d3aa4 Binary files /dev/null and b/docs/images/ImportReportWindow.PNG differ diff --git a/docs/images/ImportReportWindowSequenceDiagram.png b/docs/images/ImportReportWindowSequenceDiagram.png new file mode 100644 index 000000000000..56e6e509b383 Binary files /dev/null and b/docs/images/ImportReportWindowSequenceDiagram.png differ diff --git "a/docs/images/Invit\303\251sLogo.jpg" "b/docs/images/Invit\303\251sLogo.jpg" new file mode 100644 index 000000000000..144e2adf6b06 Binary files /dev/null and "b/docs/images/Invit\303\251sLogo.jpg" differ diff --git "a/docs/images/Invit\303\251sLogoBanner.png" "b/docs/images/Invit\303\251sLogoBanner.png" new file mode 100644 index 000000000000..01eb12446e2c Binary files /dev/null and "b/docs/images/Invit\303\251sLogoBanner.png" differ diff --git "a/docs/images/Invit\303\251sLogoSmall.png" "b/docs/images/Invit\303\251sLogoSmall.png" new file mode 100644 index 000000000000..97a5a18a21ed Binary files /dev/null and "b/docs/images/Invit\303\251sLogoSmall.png" differ diff --git "a/docs/images/Invit\303\251sLogoSmaller.png" "b/docs/images/Invit\303\251sLogoSmaller.png" new file mode 100644 index 000000000000..81169a650d6b Binary files /dev/null and "b/docs/images/Invit\303\251sLogoSmaller.png" differ diff --git a/docs/images/MailCommandActivityDiagram.png b/docs/images/MailCommandActivityDiagram.png new file mode 100644 index 000000000000..0b37fa6871ff Binary files /dev/null and b/docs/images/MailCommandActivityDiagram.png differ diff --git a/docs/images/MarkCommandSequenceDiagram.png b/docs/images/MarkCommandSequenceDiagram.png new file mode 100644 index 000000000000..cd0946d14928 Binary files /dev/null and b/docs/images/MarkCommandSequenceDiagram.png differ diff --git a/docs/images/ModelComponentClassBetterOopDiagram.png b/docs/images/ModelComponentClassBetterOopDiagram.png new file mode 100644 index 000000000000..3fd9d3ec4531 Binary files /dev/null and b/docs/images/ModelComponentClassBetterOopDiagram.png differ diff --git a/docs/images/ModelComponentClassDiagram.png b/docs/images/ModelComponentClassDiagram.png new file mode 100644 index 000000000000..83ccfbd55142 Binary files /dev/null and b/docs/images/ModelComponentClassDiagram.png differ diff --git a/docs/images/ProductWorkflow.png b/docs/images/ProductWorkflow.png new file mode 100644 index 000000000000..2c5e0e9b6d43 Binary files /dev/null and b/docs/images/ProductWorkflow.png differ diff --git a/docs/images/RemoveTagSequenceDiagram.png b/docs/images/RemoveTagSequenceDiagram.png new file mode 100644 index 000000000000..786d77ec6cd9 Binary files /dev/null and b/docs/images/RemoveTagSequenceDiagram.png differ diff --git a/docs/images/SeEduLogo.png b/docs/images/SeEduLogo.png deleted file mode 100644 index 31ad50b6f88d..000000000000 Binary files a/docs/images/SeEduLogo.png and /dev/null differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..db5524f94392 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiComponentClassDiagram.png b/docs/images/UiComponentClassDiagram.png new file mode 100644 index 000000000000..1e53f5b5c128 Binary files /dev/null and b/docs/images/UiComponentClassDiagram.png differ diff --git a/docs/images/UnmarkCommandSequenceDiagram.png b/docs/images/UnmarkCommandSequenceDiagram.png new file mode 100644 index 000000000000..172627fdf411 Binary files /dev/null and b/docs/images/UnmarkCommandSequenceDiagram.png differ diff --git a/docs/images/aaryamnus.png b/docs/images/aaryamnus.png new file mode 100644 index 000000000000..3cb6b9b71ba0 Binary files /dev/null and b/docs/images/aaryamnus.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/desktop.ini b/docs/images/desktop.ini new file mode 100644 index 000000000000..1be619bd24b7 --- /dev/null +++ b/docs/images/desktop.ini @@ -0,0 +1,2 @@ +[LocalizedFileNames] +ui_v2.png=@ui_v2,0 diff --git a/docs/images/kronicler.png b/docs/images/kronicler.png new file mode 100644 index 000000000000..4989b8d7e533 Binary files /dev/null and b/docs/images/kronicler.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/markCommandSuccess.PNG b/docs/images/markCommandSuccess.PNG new file mode 100644 index 000000000000..4da9e7440f32 Binary files /dev/null and b/docs/images/markCommandSuccess.PNG differ diff --git a/docs/images/sandhyagopakumar.png b/docs/images/sandhyagopakumar.png new file mode 100644 index 000000000000..17502ed7e2c9 Binary files /dev/null and b/docs/images/sandhyagopakumar.png differ diff --git a/docs/images/sarahtaaherbonna.png b/docs/images/sarahtaaherbonna.png new file mode 100644 index 000000000000..cd0ae7c7f1c2 Binary files /dev/null and b/docs/images/sarahtaaherbonna.png differ diff --git a/docs/images/unmarkCommandSuccess.png b/docs/images/unmarkCommandSuccess.png new file mode 100644 index 000000000000..37ded9dd71dc Binary files /dev/null and b/docs/images/unmarkCommandSuccess.png differ diff --git a/docs/images/wm28.png b/docs/images/wm28.png new file mode 100644 index 000000000000..90f62b6d05d8 Binary files /dev/null and b/docs/images/wm28.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/stylesheets/asciidoctor.css b/docs/stylesheets/asciidoctor.css index 36590bf346cd..5bd5f774cc22 100644 --- a/docs/stylesheets/asciidoctor.css +++ b/docs/stylesheets/asciidoctor.css @@ -378,7 +378,6 @@ p{margin-bottom:1.25rem} *{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} a{color:inherit!important;text-decoration:underline!important} a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} -a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} abbr[title]:after{content:" (" attr(title) ")"} pre,blockquote,tr,img,object,svg{page-break-inside:avoid} thead{display:table-header-group} diff --git a/docs/team/aaryamnus.adoc b/docs/team/aaryamnus.adoc new file mode 100644 index 000000000000..cf9ae77d4c84 --- /dev/null +++ b/docs/team/aaryamnus.adoc @@ -0,0 +1,84 @@ += Srivastava Aaryam - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Invités +== Introduction +|=== +|*_Preface:_* The purpose of this portfolio is to document my contributions, both technical and documentation-wise, to my software engineering project Invités. +|=== + +=== Project scope and my contribution +Invités is a desktop application to help *event managers and planners* better organise and manage large events. +Moreover, the application can mainly be controlled by a 3rd party using the Command Line Interface, +as well as a Graphical User Interface (GUI). + +[cols="30%,70%"] +|==== + ^.^| image:ProductWorkflow.png[] Diagram 1 - Product Workflow .^| Invités was developed as part of my team’s Software Engineering Project, and some of its *main features* include the ability to *send mass emails*, *keep track of payments*, as well as *keep track of attendance*. To add to this, by using a standardised format, the application is able to take in Comma Separated Value (CSV) files and *import* data for a particular event. + + Thanks to the complementary features my group implemented, we were able to create a streamlined workflow for event planners and managers, as depicted in *Diagram 1* on the left. +|==== + +*My contribution* involved implementing the *mass-emailing functionality* in our product, which allows you to send emails to individual, or +all of your guests. Furthermore, an added *ticket generation feature* is also available, whereby you may send guests their tickets for the event. This feature is extremely crucial as it allows event managers to +quickly inform all of their guests regarding details of the event in a swift and efficient manner, and eliminates the need of sending individual emails. Moreover, the ticket generation will also provide an +environmentally-friendly alternative to printing tickets and banners for an event. + +=== Team Invités +The team that worked with me on this project, as well as the features they implemented, is listed below: + +1. Sandhya Gopakumar - Events management +2. Sarah Taaher Bonna - Guest list management +3. Tan Tze Guang - Attendance management +4. Tan Wei Ming - Data sharing + +== Summary of contributions +|=== +| *_Preface:_* The purpose of this section is to list the contributions I have made to project Invités. +|=== + +* *Main Feature*: added the ability to *send mass emails* to guests +** *What it does*: allows you to either send emails to individual guests using the command `email`, or +send mass emails using the `emailAll` and `emailSpecific` commands. +** *Justification*: This feature will allow *event managers* to streamline their event notification process and +inform all of their guests at once, regarding details of the event or otherwise. Furthermore, ticket generation provides users a +digitised and economical solution for attendance taking once the event goes live. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=aaryamnus[Contributed code]] +* *Minor enhancement*: added an `addTag` and `removeTag` command that adds or removes a set of tags to/from all guests in the list +* *Other contributions*: +** Project management: +*** Managed releases v1.1, v1.2, v1.2.1, v1.3, v1.3.9 (5 releases) on GitHub +*** Setup Travis, AppVeyor, auto-publishing of documentation, and issue and milestone tracker +** Enhancements to existing features +*** Changed UI response for incorrect commands +*** Added a UI component to display number of guests in the current list in the footer of the application +** Community +*** Reviewed Pull Requests (PRs) for several milestones to provide team members with ample and timely feedback - [https://github.com/CS2113-AY1819S1-F09-3/main/pull/112[#112]] [[https://github.com/CS2113-AY1819S1-F09-3/main/pull/153[#153]] https://github.com/CS2113-AY1819S1-F09-3/main/pull/202[#202]] [https://github.com/CS2113-AY1819S1-F09-3/main/pull/274[#274]] [https://github.com/CS2113-AY1819S1-F09-3/main/pull/312[#312]] +*** Issues created to better structure the project workflow and ensure an iterative design process - [https://github.com/CS2113-AY1819S1-F09-3/main/issues/121[#121]] [https://github.com/CS2113-AY1819S1-F09-3/main/issues/133[#133]] [https://github.com/CS2113-AY1819S1-F09-3/main/issues/178[#178]] [https://github.com/CS2113-AY1819S1-F09-3/main/issues/239[#239]] +** Tools +*** Integrated a third party library to enable email communication https://javaee.github.io/javamail/docs/api/[JavaMail API] + +== Contributions to the User Guide + +|=== +|*_Preface:_* This section details the contributions I have made to the User Guide, and shows my ability to write documentation targeting end-users. +|=== + +include::../UserGuide.adoc[tag=addremovetag] + +include::../UserGuide.adoc[tag=email] + +include::../UserGuide.adoc[tag=settingupemail] + +== Contributions to the Developer Guide + +|=== +|*_Preface:_* This section details the contributions I have made to the Developer Guide, and shows my ability to write technical documentation to explain the *implementation of my contributions*, as well as *methods to allow manual user-testing*. +|=== + +include::../DeveloperGuide.adoc[tag=emailimplementation] + +include::../DeveloperGuide.adoc[tag=emailappendix] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== 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] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/kronicler.adoc b/docs/team/kronicler.adoc new file mode 100644 index 000000000000..b6455902c178 --- /dev/null +++ b/docs/team/kronicler.adoc @@ -0,0 +1,115 @@ += Tan Tze Guang - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Invités + +--- + +== Overview + +|=== +|_PREFACE_ +|_This section features the role and contributions I had over the course of this project_ +|=== + +Invités is a desktop application used by people who manage and plan events to streamline their processes and better manage +the events that they are handling. + +The user interacts with it mainly using a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with the JavaFX library. + +*Responsibility* - In this project, I was assigned the task of creating an easy to use system to manage the attendance within the application. + + +To implement a feature like that in this application, I would need a method to uniquely identify each guest in the guest list. This is necessary as having identifiers that can give more than one result +will increase the burden of the event planner as they will have to choose which entry would be modified, when this application is supposed to be helping them. +Identifying the right person quickly in an event is essential for running the event smoothly. Also considering the fact +that names, phone numbers, emails cannot be considered unique in a setting such as a public event, creating a unique ID (UID) was needed. +After creating the UID, the process of marking attendance is made easier as each UID represents one person in the guest list. + +*Project scope* - Invités is an event management system that provides an intuitive platform for event planners to better organise and manage +the reception during small or large events such as school orientation camps, committee events or more formal occasions such as weddings. +The main features that streamline the processes of the event planner is the ability to send mass emails to attendees, +keep track of payments, as well as attendance tracking during the event itself. If the event planner used other applications +to store his data, they can easily import their data over from CSV files that can be generated from Microsoft Excel. + +== Team +The list below shows all our team members and what we have contributed to the application _Invités_: + +|=== +|*Names* | *Major Feature* +|Aaryam Srivastava | Mass Email Communication +|Sandhya Gopakumar | Events Management +|Sarah Taaher Bonna | Guest List Management +|Tan Tze Guang | Attendance Management +|Tan Wei Ming | Data Sharing +|=== +== Summary of contributions + +|=== +|_PREFACE_ +|_This section is a summary of my contributions to the project. It showcases my ability to work on a software project with a team of people_ +|=== + +* *Major Feature*: added the function to mark attendance of the attendees in the Guest list +** What it does: This function allows the person planning an event to keep track of the number of attendees whom have arrived at the event by marking them as present or absent during the event itself. +** Justification: This feature is necessary in the implementation of our guest list as it is a core feature that gives the event planner peace of mind as it is a tool for them to ensure that the event is run smoothly. +** Highlights: This feature is a core feature that required creating the Unique ID for each guest, else it would have to be more complex to handle identification mediums that could potentially identify more than one person at a time. + +* *Sub feature*: added the function to generate Unique ID (UID) for attendees +** What it does: This function allows the event planner to not have to think of a large number of UID for guests of the event. +** Justification: This feature is necessary in the implementation of our guest list as it streamlines the identification of each guest especially for the receptionists for the event. It provides a unique identification to each guest which allows our program to pick out the right person without needing to handle people whom have the same name or same phone numbers. +** Highlights: This enhancement affects existing commands and commands to be added in future. The implementation too was challenging as it required changes to existing commands. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=kronicler[Functional code]] + +* *Other contributions*: + +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com/CS2113-AY1819S1-F09-3/main/pull/190[#190] https://github.com/CS2113-AY1819S1-F09-3/main/pull/139[#139] https://github.com/CS2113-AY1819S1-F09-3/main/pull/313[#313] +*** Added more documentation in to the existing contents of the Developer Guide: https://github.com/CS2113-AY1819S1-F09-3/main/pull/190[#190] https://github.com/CS2113-AY1819S1-F09-3/main/pull/139[#139] https://github.com/CS2113-AY1819S1-F09-3/main/pull/313[#313] +** Enhancements to existing features: +*** Added generation of UID into the `add_guest` command +** Community: +*** Reported bugs and suggestions for another team in the cohort (examples: https://github.com/CS2113-AY1819S1-F10-3/main/issues/187[#187] https://github.com/CS2113-AY1819S1-F10-3/main/issues/205[#205]) +*** Reviewed Pull requests from team members to give timely feedback (examples: https://github.com/CS2113-AY1819S1-F09-3/main/pull/135[#135] https://github.com/CS2113-AY1819S1-F09-3/main/pull/138[#138] https://github.com/CS2113-AY1819S1-F09-3/main/pull/152[#152] https://github.com/CS2113-AY1819S1-F09-3/main/pull/157[#157] https://github.com/CS2113-AY1819S1-F09-3/main/pull/203[#203] https://github.com/CS2113-AY1819S1-F09-3/main/pull/232[#232] https://github.com/CS2113-AY1819S1-F09-3/main/pull/284[#284]) +** Tools: +*** Integrated a Github plugin (CircleCI) to my personal forked repository + +== Contributions to the User Guide + +|=== +|_PREFACE_ +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation which targets end-users._ +|=== + +[NOTE] +The implementation of the `mark` and `unmark` commands are found in the Logic component of the application. + +include::../UserGuide.adoc[tag=mark] + +include::../UserGuide.adoc[tag=unmark] + +include::../UserGuide.adoc[tag=start_marking] + +include::../UserGuide.adoc[tag=stop_marking] + +== Contributions to the Developer Guide + +|=== +|_PREFACE_ +|_Given below are sections that I have contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +[NOTE] +The implementation of the `mark` and `unmark` commands are found in the Logic component of the application. + +include::../DeveloperGuide.adoc[tag=markunmark] + +[NOTE] +The implementation of the generate Unique ID (UID) function is found in the Logic component of the application within the `add_guest` command. + +include::../DeveloperGuide.adoc[tag=generateuid] + +include::../DeveloperGuide.adoc[tag=generateuidappendix] + +include::../DeveloperGuide.adoc[tag=attendanceappendix] diff --git a/docs/team/sandhyagopakumar.adoc b/docs/team/sandhyagopakumar.adoc new file mode 100644 index 000000000000..9157329a537c --- /dev/null +++ b/docs/team/sandhyagopakumar.adoc @@ -0,0 +1,89 @@ += Sandhya Gopakumar - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Invités + +== Overview + +The purpose of this portfolio is to document my role and contributions to my team's project, Invités, +for the module: Software Engineering and Object-Oriented Programming(CS2113T) at the National University of Singapore. + +==== _Project Scope_ + +Invités is a desktop application that caters to event managers and planners. +The application strives to aid our target users through features such as mass-email communication, guest list filtration, attendance marking by unique ID, import or export of CSVs containing guest lists and event details management. +The application works on a Command Line Interface(CLI), where users key in the commands specified in our User Guide into a command box to perform a task. +We have also provided a Graphical User Interface(GUI). +The application was programmed using Java and has approximately 10k lines of code. + +==== _My responsibility - Event details Management_ + +My task involved the creation of an event details component that lets our users input the confirmed event details. I completed this task by implementing the following steps: + +* Creating an event model with all the required classes and editing the existing model + +* Handling the storage component for event details + +* Creating a user interface component for event details + +* Creating the 'add_event', 'delete_event' and 'edit_event' commands to add, delete and edit event details in our application + +==== _Teammates_ + +The team that worked with me on this project and the major feature they implemented are as follows: + + 1. Sarah Taaher Bonna - Guest List Management + 2. Aaryam Srivastava - Mass Email Communication + 3. Tan Tze Guang - Attendance Management + 4. Tan Wei Ming - Data Sharing + + + +== Summary of contributions +|=== +|_The purpose of this section is to list my contributions to Invités._ +|=== + +* *Feature*: Added *the event details component* +** _What it does_: Allows the user to enter the confirmed details for the event they are organising. +** _Justification_: This feature improves the product significantly because a user will want to view specific details that they need to prioritise/reiterate in the days leading up to the event. A count of the days left to the user's event is also displayed for the user's benefit. +** _Highlights_: This enhancement affects the existing logic, model, user interface and storage components as it involves the creation of a new model, the add_event ,delete_event and edit_event commands, an additional storage class as well as a user interface component. +** _Credits_: Reused some ideas from the SE-EDU AddressBook Level 4 + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=sandhyagopakumar[Functional and test code]] + +* *Other contributions*: +** Morphed the add, delete, edit commands to implement the add_guest, delete_guest and edit_guest commands. +** Contributed and edited multiple automated tests +** _Documentation_: +*** Edited README.adoc to include a screenshot of our user interface: [https://github.com/CS2113-AY1819S1-F09-3/main/pull/317[317]] +*** Tweaked use cases and existing diagrams in the Developer guide. Edited descriptions of our commands in the User Guide to reflect our enhancements: [https://github.com/CS2113-AY1819S1-F09-3/main/pull/83[83]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/174[174]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/175[175]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/186[186]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/279[279]] +** _Community_: +*** Reported bugs and suggestions for another team in the cohort: [https://github.com/CS2113-AY1819S1-T12-1/main/issues/127[127]], [https://github.com/CS2113-AY1819S1-T12-1/main/issues/133[133]], [https://github.com/CS2113-AY1819S1-T12-1/main/issues/146[146]] + +== Contributions to the User Guide + +|=== +|_Given below are the sections that I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +=== *_Sections 3.19, 3.20 and 3.21 of the User Guide_* + +include::../UserGuide.adoc[tag=event] + +== Contributions to the Developer Guide +//TODO: +|=== +|_Given below are sections that I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +=== *_1. Sections 4.3 and 4.4 of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=event] + + +=== *_2. Appendix F.4, F.5 and F.6 of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=eventappendix] diff --git a/docs/team/sarahtaaherbonna.adoc b/docs/team/sarahtaaherbonna.adoc new file mode 100644 index 000000000000..271823017e8c --- /dev/null +++ b/docs/team/sarahtaaherbonna.adoc @@ -0,0 +1,120 @@ += Sarah Taaher Bonna - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Invités + +--- +== Overview + +|=== +|_The purpose of this portfolio is to document my role and contributions to the project._ +|=== + +*Invités* - An event management system. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +*Responsibility* - My task was to handle data filtration. I split this task into two. First, I created a Filter command +to handle data filtration for the payment status, attendance status and the tags, as these fields +are not unique to any person. Next, I modified the existing Find command to handle names, +phone numbers and email addresses, as these are fields used for identification, and the user may want to find a few +guests at one go. Rather than use the Filter command, which would yield results one-by-one, +the Find command allows you to find a few guests at the same time. + +*Project Scope* - Invités is an application targeted at event managers and planners, +that allows you to better organise, cater, and manage the reception for large events +such as weddings, school gatherings, orientation camps, etc. Some of the main features +include the ability to send mass emails, keep track of payments, as well as the tab +keeping of attendance of the guests. To add to this, by employing a standardised format, +the application is able to take in Comma Separated Values (CSV) files and import data for +a particular event. This removes your need to input all the information manually, +as well as subsequently, gives you an alternative if you decide to organise another event +using the same guest list. + +This will give you an easier time to manage the reception as you will be equipped with +tools that will minimise errors in catering, organising, and ordering. Moreover, this +application aims to reduce the frustrations for you when planning events and address your +needs in a platform that is more user-friendly, personalised, and efficient. + +*Team:* + +1. Aaryam - Mass Email Communication +2. Sandhya - Events Management +3. Sarah - Guest List Management +4. Tze Guang - Attendance Management +5. Wei Ming - Data Sharing + +== Summary of contributions + +|=== +|_The purpose of this section is to list the contributions I have made to Invités._ +|=== + +* *Major enhancement*: Added *the ability to filter data according to payment status, attendance +status and tags* +** What it does: allows the user to filter data based on keywords provided by you, + such as payment status, attendance status and/or tags, depending on the prefix used. +** Justification: This feature improves the product significantly because you can find guests who have yet to pay or are absent on the day of the event. + This will help you to be able to find people that fit the conditions you are looking for, without having to traverse the list manually. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=sarahtaaherbonna[Sarah Taaher Bonna]] + +* *Other contributions*: + +1. Created Payment and Attendance attributes of a person. +Ensured that these attributes can only accept certain values. + +2. Modified find command which allows the user to find guests with the name(s), +phone number(s) and/or email address(es), depending on the prefix used. +This modified feature improves the product because you can find a group of people, using their +name(s), phone number(s) and/or email address(es) at one go, instead of doing it one-by-one. As such, +you are able to find the necessary guests using any available contact details, not just the name. + +** Enhancements to existing features: +*** Wrote additional tests for existing and new features to increase coverage (Pull requests [https://github.com/CS2113-AY1819S1-F09-3/main/pull/273[273]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/266[266]]) +*** Did cosmetic tweaks to existing mark command to show updated list +(for example if filter a/absent, and then mark is performed, the person who has been +marked will no longer be in the list): [https://github.com/CS2113-AY1819S1-F09-3/main/pull/283[283]] +** Documentation: +*** Did cosmetic tweaks to existing contents of the Developer Guide + and made important changes to the Use Cases: [https://github.com/CS2113-AY1819S1-F09-3/main/pull/144[144]] +*** Did cosmetic tweaks to existing contents of the User Guide: [https://github.com/CS2113-AY1819S1-F09-3/main/pull/218[218]], [https://github.com/CS2113-AY1819S1-F09-3/main/pull/266[266]], [https://github.com/CS2113-AY1819S1-F09-3/main/commit/09652199e0a7f2985c93fb907e345f357150449d[Commit 0965219]] +*** Added in the purpose of Invités in README.adoc: [https://github.com/CS2113-AY1819S1-F09-3/main/pull/2[2]] +** Community: +*** Reported bugs and suggestions for another team in the cohort (examples: [https://github.com/CS2113-AY1819S1-T09-4/main/issues/137[137]], [https://github.com/CS2113-AY1819S1-T09-4/main/issues/144[144]]) + + +== Contributions to the User Guide + + +|=== +|_Given below are the sections that I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +=== *_Sections 3.3 and 3.15 of the User Guide_* + +include::../UserGuide.adoc[tag=filter] + +include::../UserGuide.adoc[tag=find] + + +== Contributions to the Developer Guide + +|=== +|_Given below are the sections that I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +=== *_Sections 4.1 and 4.2 of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=filter] + +include::../DeveloperGuide.adoc[tag=find] + +=== *_Appendix F.2 and F.3 of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=filterappendix] + +include::../DeveloperGuide.adoc[tag=findappendix] + + + +--- diff --git a/docs/team/wm28.adoc b/docs/team/wm28.adoc new file mode 100644 index 000000000000..c64de57cd437 --- /dev/null +++ b/docs/team/wm28.adoc @@ -0,0 +1,72 @@ += Tan Wei Ming - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:repoURL: https://github.com/CS2113-AY1819S1-F09-3/main/blob/master + +== PROJECT: Invités +|=== +|_This portfolio documents my contributions to Invités._ +|=== + +Invités is a desktop application that helps people to organize and manage events. It simplifies and streamlines common event management tasks such as managing a guest list, attendance taking, QR code ticketing and mass email sending. With our application, event planners are able save time and effort as it offers a much more beginner friendly and specialized workflow as compared to its competition such as Microsoft Excel or Google Sheet. It is also designed to be highly versatile and can be used to plan and manage a wide range of events from school camps to weddings. + +The application is developed in Java and has a graphical user interface created with JavaFX. + +Invités is developed as part of a team project for a software development module in National University of Singapore. I was tasked with the responsibility to implement a data sharing mechanism for the guest data. Below is a breakdown of our various roles in the team, which consists of five members. + +* http://github.com/aaryamNUS[Srivastava Aaryam]: Mass email communication +* http://github.com/SandhyaGopakumar[Sandhya Gopakumar]: Events management +* http://github.com/SarahTaaherBonna[Sarah Taaher Bonna]: Guest list management +* http://github.com/kronicler[Tan Tze Guang]: Attendance management +* http://github.com/wm28[Tan Wei Ming]: Data sharing + +== Summary of contributions +|=== +|_This section summarizes my main contributions to the team. It showcases my ability to develop software features and work on software as a team._ +|=== + +* *Major enhancement*: added *the ability share data by importing/exporting guests to Comma Separated Values(CSV) files* +** What it does: Allows event planners to export/import guests along with their details to CSV files. For unsuccessful imports, an import report window will be generated to show the malformed data and its associated errors. +** Justification: The import feature is critical to the application because it allows event planners to save time by adding guests in batches. The export feature also allows event planners to share guest data easily. +** Highlights: This enhancement is designed to support future file formats easily and it required an in-depth analysis of several design alternatives. +* *Minor enhancement*: Added utility class to generate QR codes for ticketing (Credits: https://mvnrepository.com/artifact/com.google.zxing/core[XZing Core]) + +* *Code contributed*: https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=wm28[RepoSense link] +* *Other contributions*: + +** Project management: +*** Managed releases `v1.2.9`, `v1.3.1` (2 releases) on GitHub +*** Setup about us page (Pull requests https://github.com/CS2113-AY1819S1-F09-3/main/pull/26[#26]) +*** Designed initial user interface mock up in Adobe XD (Pull request https://github.com/CS2113-AY1819S1-F09-3/main/pull/27[#27]) +** Enhancements to existing features: +*** Remodelled the guest list and added a guest preview panel in the user interface (Pull requests https://github.com/CS2113-AY1819S1-F09-3/main/pull/88[#88], https://github.com/CS2113-AY1819S1-F09-3/main/pull/100[#100], https://github.com/CS2113-AY1819S1-F09-3/main/pull/152[#152]) +** Community: +*** Reported bugs for other teams in the class (examples: https://github.com/CS2113-AY1819S1-W12-3/main/issues/153[1], https://github.com/CS2113-AY1819S1-W12-3/main/issues/148[2]) +*** Reviewed pull requests (with non-trivial review comments) (https://github.com/CS2113-AY1819S1-F09-3/main/pull/272[#272], https://github.com/CS2113-AY1819S1-F09-3/main/pull/232[#232]) + +== 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=import] +include::../UserGuide.adoc[tag=export] +include::../UserGuide.adoc[tag=csvformat] + + +== 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._ +|=== + +=== *_Implementation section of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=importexport] + +=== *_Appendix section of the Developer Guide_* + +include::../DeveloperGuide.adoc[tag=importexportappendix] +include::../DeveloperGuide.adoc[tag=personpreviewappendix] diff --git a/docs/templates/_header.html.slim b/docs/templates/_header.html.slim index 1995d26a1615..91de7a3d87a8 100644 --- a/docs/templates/_header.html.slim +++ b/docs/templates/_header.html.slim @@ -1,44 +1,18 @@ / NOTE: You must restart the gradle daemon after modifying any template file for the changes to take effect. -- if !(attr? 'no-site-header') && (attr? 'site-seedu') - #seedu-header - nav.navbar.navbar-lg.navbar-light.bg-lighter - .container - a.navbar-brand href='https://se-edu.github.io/' - img src=(site_url 'images/SeEduLogo.png') alt='SE-EDU' - ul.navbar-nav - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level1' AB-1 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level2' AB-2 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level3' AB-3 - li.nav-item - a.nav-link.active href=(site_url 'index.html') AB-4 - li.nav-item - a.nav-link href='https://se-edu.github.io/collate' Collate - li.nav-item - a.nav-link href='https://se-edu.github.io/se-book' Book - li.nav-item - a.nav-link href='https://se-edu.github.io/learningresources' Resources - - if !(attr? 'no-site-header') #site-header nav.navbar.navbar-light.bg-light .container - if attr? 'site-name' + img src=(site_url 'images/InvitésLogoBanner.png') a.navbar-brand href=(site_url 'index.html') =(attr 'site-name') ul.navbar-nav li.nav-item =nav_link('UserGuide', 'UserGuide.html', 'User Guide') li.nav-item =nav_link('DeveloperGuide', 'DeveloperGuide.html', 'Developer Guide') - - if attr? 'site-seedu' - li.nav-item - =nav_link('LearningOutcomes', 'LearningOutcomes.html', 'LOs') li.nav-item =nav_link('AboutUs', 'AboutUs.html', 'About Us') - li.nav-item - =nav_link('ContactUs', 'ContactUs.html', 'Contact Us') - if attr? 'site-githuburl' li.navitem a.nav-link href=(attr 'site-githuburl') diff --git a/errorsample.csv b/errorsample.csv new file mode 100644 index 000000000000..34c404d5beb0 --- /dev/null +++ b/errorsample.csv @@ -0,0 +1,3 @@ +Charlotte Oliveiro,93210283,abcdgmail.com,NOTPAID,PRESENT,00002,gold,Normal +Irfan Ibrahim,92492021,testing@gmail.com,PAID,PRSENT,00004,silver,Halal +Irfan Ibrahim,924021,testng@gmail.com,PAD,PRESENT,00004,silver,Halal diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..1b93781c4413 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue Oct 23 18:05:29 SGT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/sample.csv b/sample.csv new file mode 100644 index 000000000000..02e00d6c89a6 --- /dev/null +++ b/sample.csv @@ -0,0 +1,22 @@ +Bernice Yu,99272758,abc@gmail.com,NOTPAID,ABSENT,00001,gold,Veg +Charlotte Oliveiro,93210283,abcd@gmail.com,NOTPAID,PRESENT,00002,gold,Normal +David Li,83186624,abcde@gmail.com,PENDING,ABSENT,00003,Veg,bronze +Irfan Ibrahim,92492021,testing@gmail.com,PAID,PRESENT,00004,silver,Halal +Roy Balakrishnan,92624417,123rs@u.nus.edu,PENDING,ABSENT,00005,Veg,silver +Alma Andrade,81336837,zccvxv@gmail.com,PAID,ABSENT,00006,gold,LactoseIntolerant +Eamon Webster,80000030,cxvxcvcx@gmail.com,PAID,ABSENT,00007,Normal,bronze +Edan Morse,80000200,xcxc@u.nus.edu,NOTPAID,ABSENT,00008,gold,GlutenFree +ElsieMae Vasquez,80000044,Zxccsaq@gmail.com,NOTPAID,ABSENT,00009,silver,Normal +Firat Sweeney,80000889,Zczwx@sgmail.com,PAID,ABSENT,00010,Normal,bronze +Franklin Snider,80000111,zxwsjyyjiun@gmail.com,PAID,ABSENT,00011,Normal,bronze +Hakeem Salgado,80000957,hmnbfdgdhr@gmail.com,PAID,ABSENT,00012,silver,Vegan +Hibah Lees,80000882,3fdg45f@gmail.com,NOTPAID,ABSENT,00013,Kosher,bronze +Jayda Hill,80000251,fdsjbfds@gmail.com,PAID,ABSENT,00014,gold,PeanutAllergy +Keiren Reeve,80000901,sdf3874@u.nus.edu,PAID,ABSENT,00015,silver,Normal +Kinsley Coffey,80000817,Testinghbhfds@gmail.com,NOTPAID,ABSENT,00016,GlutenFree,bronze +Liliana Howard,80000211,324njndsfmk@gmail.com,PAID,ABSENT,00017,silver,Normal +Maleeha Tapia,80000909,o872ryufji@u.nus.edu,NOTPAID,ABSENT,00018,gold,Halal +Monty Yu,80000786,4p98h43jbfdkjb@gmail.com,PAID,ABSENT,00019,ShrimpAllergy,bronze +Samiya Todd,80000699,rufjblkjb@gmail.com,PAID,ABSENT,00020,silver,Normal +Sohail Beattie,80000505,gflklkn@gmail.com,NOTPAID,ABSENT,00021,gold,Vegan +Sanjukta Saha,90128773,glhf@gmail.com,PENDING,PRESENT,00022,Veg, diff --git a/src/main/docs/HelpWindow.html b/src/main/docs/HelpWindow.html new file mode 100644 index 000000000000..5207658e37a3 --- /dev/null +++ b/src/main/docs/HelpWindow.html @@ -0,0 +1,1389 @@ +Invités - User Guide + +
+
+

1. Introduction

+
+
+

Invités is an application targeted at event managers and planners. The application allows you to efficiently organise, cater, and manage large events such +as weddings, school gatherings, orientation camps, etc. Some of the main features include the ability to send mass emails, keep track of payments and take note of attendance of the guests. +To add to these features, by employing a standardised format, the application is able to take in Comma Separated Values (CSV) files and import data for a particular event. +This eliminates the need to input all the information manually and subsequently, gives you an alternative if you decide to organise another event using the same guest list.

+
+
+

This will give you an easier time while managing the reception at your event as you will be equipped with tools that will minimise errors in catering, +organisation, and orders. Moreover, this application aims to reduce your frustration while planning events by addressing your needs in a platform that is more user friendly, personalised, and +efficient.

+
+
+

The main purpose of this User Guide is to give you a detailed overview of all the features in our application and how to use them.

+
+
+
+
+

2. Quick Start

+
+
+
    +
  1. +

    Ensure you have Java version 9 or later installed in your Computer.

    +
  2. +
  3. +

    Download the latest jar file from the GIT repository here.

    +
  4. +
  5. +

    Place the jar file where your home directory resides

    +
  6. +
  7. +

    Double click on the jar file and wait a couple of seconds as the application loads. If you’re successful, a main screen will load +similar to the one shown below:

    +
    +
    +Ui +
    +
    +
  8. +
  9. +

    As the Command Line Input is your primary method of manipulation, here are some +example commands that you can use:

    +
    +
      +
    • +

      help : Displays a summary of the list of commands that the application offers.

      +
    • +
    • +

      add_guest : Adds a guest to the guest list.

      +
    • +
    • +

      delete_guest : Deletes an entry from the guest list.

      +
    • +
    • +

      mark : Marks the attendance of a guest from ABSENT to PRESENT.

      +
    • +
    • +

      exit : exits the application.

      +
    • +
    +
    +
  10. +
  11. +

    You should type the command in the command box and press Enter to execute it.
    +e.g. typing help and pressing Enter will open the help window.

    +
  12. +
  13. +

    Please refer to Section 3, “Features” subsection to see more detailed documentation of the functions that +are included in this application

    +
  14. +
  15. +

    If you would like to perform emailing services through our application, please complete the steps as shown +in Section 5, “Enabling Email Services”

    +
  16. +
+
+
+
+
+

3. Features

+
+
+
+
+

Command Format

+
+
+
    +
  • +

    Words in UPPER_CASE are the parameters to be supplied by you e.g. in add_guest n/NAME, NAME is a parameter which can be used as add_guest n/Bob Lee.

    +
  • +
  • +

    Items in square brackets are optional e.g n/NAME [t/TAG] can be used as n/Bob Lee t/VIP or as n/Bob Lee.

    +
  • +
  • +

    Items with ​ after them can be used multiple times including zero times e.g. [t/TAG]…​ can be used as   (i.e. 0 times), t/VIP, t/VIP t/Vegetarian etc.

    +
  • +
  • +

    You can specify parameters in any order e.g. if the command specifies n/NAME p/PHONE_NUMBER, p/PHONE_NUMBER n/NAME is also acceptable.

    +
  • +
+
+
+
+
+

3.1. Viewing help : help

+
+

Displays a summary of the list of commands that the application offers
+Format: help

+
+
+
+

3.2. Adding a guest: add_guest

+
+

Adds a guest to the guest list.
+No spaces or special characters allowed in Payment and Attendance.
+Payment accepts "PAID", "NOTPAID" , "PENDING" or "N.A.". +Attendance accepts "ABSENT", "PRESENT" or "N.A."
+Payment and attendance are case-insensitive.
+If any options other than the ones given are entered, the guest will be added if +other fields are fine, but payment and/or attendance will be blank.
+Format: add_guest n/NAME p/PHONE_NUMBER e/EMAIL a/PRESENT pa/PAYMENT [t/TAG]…​

+
+
+ + + + + +
+ + +A guest can have any number of tags (including 0) +
+
+
+

Examples:

+
+
+
    +
  • +

    add_guest n/Bob Lee p/81720172 e/boblee@gmail.com a/Absent pa/NOTPAID t/VIP t/Vegetarian

    +
  • +
  • +

    add_guest n/John Doe p/91028392 e/johndoe@gmail.com a/Present pa/PAID t/Groom t/NonVegetarian

    +
  • +
+
+
+
+

3.3. Listing all guests : list

+
+

Shows a list of all guests in the guest list.
+Format: list

+
+
+
+

3.4. Editing a guest : edit_guest

+
+

Edits an existing guest entry in the guest list.
+No spaces or special characters allowed in Payment and Attendance.
+Payment accepts "PAID", "NOTPAID" , "PENDING" or "N.A.". +Attendance accepts "ABSENT", "PRESENT" or "N.A."
+Payment and attendance are case-insensitive.
+If any options other than the ones given are entered, the guest will be edited, +but payment and/or attendance will be blank.
+Format: edit_guest INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/PRESENT] [pa/PAID] + [t/GUEST_TYPE] [t/DIET]…​

+
+
+
+
+
    +
  • +

    Edits the guest at the specified INDEX. The index refers to the index number shown in the displayed guest list. The index must be a positive integer 1, 2, 3, …​

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Existing values will be updated to the input values.

    +
  • +
  • +

    When editing tags, the existing tags of the guest will be removed i.e adding of tags is not cumulative.

    +
  • +
  • +

    You can remove all the guest’s tags by typing t/ without specifying any tags after it.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    edit_guest 2 n/Bob Chan
    +Edits the name of the 2nd guest to be Bob Chan respectively.

    +
  • +
+
+
+
+

3.5. Deleting a guest : delete_guest

+
+

Deletes the specified guest from the guest list.
+Format: delete_guest INDEX

+
+
+
+
+
    +
  • +

    Deletes the guest at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed guest list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    list
    +delete_guest 2
    +Deletes the 2nd guest in the guest list.

    +
  • +
  • +

    find n/Betsy
    +delete_guest 1
    +Deletes the 1st guest in the results of the find command.

    +
  • +
+
+
+
+

3.6. Removing a set of tags from all guests : removeTag

+
+

This command allows you to remove a set of tags from all guests in the guest list.
+Format: removeTag [t/TAG]…​

+
+
+
+
+
    +
  • +

    The removeTag command will remove any number of tags provided by you from all guests

    +
  • +
  • +

    If the tags you provide are not shared by any of the guests in the current list, you will be informed of this

    +
  • +
  • +

    You must provide the tags to be removed, an input of removeTag t/ will not do anything

    +
  • +
  • +

    You must provide tags that are alphanumeric, otherwise the system will inform you of the error

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    removeTag t/Veg t/VIP
    +You will remove the tags Veg and VIP from all guests in the current list

    +
  • +
  • +

    removeTag t/
    +This will present you with an error in specifying the command, as you have not provided any tags to delete

    +
  • +
  • +

    removeTag t/@!*
    +This will present you with an error in specifying the command, as all your tags must be alphanumeric

    +
  • +
+
+
+
+

3.7. Adding a set of tags to all guests : addTag

+
+

This command allows you to add a set of tags to all guests in the guest list.
+Format: addTag [t/TAG]…​

+
+
+
+
+
    +
  • +

    The addTag command will add any number of tags provided by you to all guests

    +
  • +
  • +

    You must provide tags that are alphanumeric, otherwise the system will inform you of the error in your input format

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    addTag t/Veg t/VIP
    +You will add the tags Veg and VIP to all guests in the current list

    +
  • +
  • +

    addTag t/@
    +This will present you with an error in specifying the command, as all your tags must be alphanumeric

    +
  • +
+
+
+
+

3.8. Locating guests by name, phone number or email address: find

+
+

Find guests whose names, phone numbers and/or email addresses +contain any of the given keywords.
+Format: find n/KEYWORD p/MORE_KEYWORDS e/MORE_KEYWORDS
+Example: find n/NAME p/PHONE e/EMAIL

+
+
+
+
+
    +
  • +

    The search is case-insensitive. e.g n/hans will match n/Hans

    +
  • +
  • +

    The order of the keywords does not matter. e.g. n/Hans n/Bo will match n/Bo n/Hans

    +
  • +
  • +

    Only names, phone numbers and email addresses are searched, depending on prefixes given.

    +
  • +
  • +

    Only full words will be matched e.g. n/Han will not match n/Hans

    +
  • +
  • +

    Guests matching at least one keyword will be returned (i.e. OR search). e.g. n/Hans n/Bo will return Hans Gruber, Bo Yang

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    find n/John
    +You will be shown a list that contains the entries of john and John Doe

    +
  • +
  • +

    find n/Betsy n/Tim n/John
    +You will be show a list containing entries of any guest having names Betsy, Tim, or John

    +
  • +
  • +

    find n/alex p/92746838 e/johndoe@gmail.com
    +You will be show a list containing entries of any guest having the name Alex, +phone number 92746838, or email address johndoe@gmail.com

    +
  • +
  • +

    find n/david n/edan
    +You will be shown a list that contains the entries of any guests having the +name david and edan

    +
  • +
  • +

    find n/david edan
    +You will be shown a list that contains the entries of any guests having the +name david but not edan

    +
  • +
+
+
+
+

3.9. Import guests from CSV file : import

+
+

Imports guests with data from a specified CSV file. The structure for the guest fields in the CSV file is predefined and can be found at CSV Guest List Format.

+
+
+

Format: import FILE_PATH

+
+
+
+
+
    +
  • +

    No existing guest in the guest list will be deleted due to the import.

    +
  • +
  • +

    FILE_PATH shall only be a relative or an absolute file path.

    +
    +
      +
    • +

      Relative file path is relative to where the application Jar file is located.

      +
    • +
    +
    +
  • +
  • +

    There is no guaranteed ordering of guests after each import.

    +
  • +
  • +

    Importation of guests which already exist will be skipped.

    +
    +
      +
    • +

      A guest will be classified as an existing guest if it has the same name and matching phone number or email address with an existing guest in the guest list.

      +
    • +
    +
    +
  • +
  • +

    Importation of badly formatted guests will be skipped.

    +
  • +
  • +

    The CSV guest entries which are badly formatted or those which corresponds to an existing guest in the guest list, will trigger an import report window as shown in figure 1.

    +
  • +
+
+ +++ + + + + + +

ImportReportWindow Figure 1 - ImportReportWindow: Shows the offending CSV guest entries with their associated error messages.

+
+
+
+

Examples:

+
+
+
    +
  • +

    import directory/subdirectory/guestlist.csv
    +You will populate the guest list with the data imported from the CSV file in the specified path.

    +
  • +
+
+
+
+

3.10. Exporting guests to CSV file : export

+
+

Exports guests' data to a specified CSV file. The structure for the guest fields in the CSV file is predefined and can be found at CSV Guest List Format.

+
+
+

Format: export FILE_PATH

+
+
+
+
+
    +
  • +

    FILE_PATH shall only be a relative or an absolute file path.

    +
    +
      +
    • +

      Relative file path is relative to where the application Jar file is located.

      +
    • +
    +
    +
  • +
  • +

    If no FILE_PATH is provided, guests will be exported using the default filename, exportedGuestBook.csv to the current working directory (folder where the application Jar file is located).

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    export directory/subdirectory/guestlist.csv
    +You will export the currently filtered guest list entries into a CSV file in the specified path.

    +
  • +
+
+
+
+

3.11. Marking a guest as present : mark

+
+

Marks a guest as present using a unique number assigned to them. This will also change the +a/PRESENT​ tag associated with the guest to Present.
+Format: mark [PHONE_NUMBER]

+
+
+ + + + + +
+ + +PHONE_NUMBER does not use the p/ prefix.
+PHONE_NUMBER only accepts a string of numbers, other characters will trigger an invalid command format error. +
+
+
+

Examples:

+
+
+
    +
  • +

    mark 81927291
    +You will mark the guest with phone number 81927291 as present.

    +
  • +
+
+
+
+

3.12. Marking a guest as absent : unmark

+
+

Marks a guest as absent using their unique number. This will also change the +a/PRESENT​ tag associated with the guest to Absent.
+Format: unmark [PHONE_NUMBER]

+
+
+ + + + + +
+ + +PHONE_NUMBER does not use the p/ prefix.
+PHONE_NUMBER only accepts a string of numbers, other characters will trigger an invalid command format error. +
+
+
+

Examples:

+
+
+
    +
  • +

    unmark 81927291
    +You will mark the guest with phone number 81927291 as absent.

    +
  • +
+
+
+
+

3.13. Start mass attendance marking : start_marking - Coming in v2.0

+
+ + + + + +
+ + +This feature is not implemented yet +
+
+
+

Start the mass attendance marking mode. Allows you to mark attendance without using +the mark prefix.
+Format: start_marking [TICKET_ID]…​

+
+
+

Examples:

+
+
+
    +
  • +

    start_marking + 87654321 + 87654322 + 87654323 + 87654324…​
    +You will mark the guests with unique numbers 87654321, 87654322, 87654323, 87654324 as present

    +
  • +
+
+
+
+

3.14. Stop mass attendance marking : stop_marking - Coming in v2.0

+
+ + + + + +
+ + +This feature is not implemented yet +
+
+
+

Stop the mass attendance marking mode.
+Format: stop_marking

+
+
+
+

3.15. Filter list based on specified parameters : filter

+
+

Filter guest list based on filter guest attributes. Only filters based on +payment status, attendance status and tags. Keywords should not have spaces or any +special character.
+Values accepted for Payment Status: PAID, NOTPAID, PENDING or N.A.
+Values accepted for Attendance Status: PRESENT, ABSENT, N.A.
+Format: filter [pa/PAYMENT_STATUS] [a/ATTENDANCE_STATUS] + [t/DIET] [t/GUEST_TYPE] [t/…​]

+
+
+
+
+
    +
  • +

    The search is case-insensitive. e.g pa/paid will match pa/PAID

    +
  • +
  • +

    The order of the keywords does not matter. e.g. pa/PAID a/ABSENT will match a/ABSENT pa/PAID

    +
  • +
  • +

    Only payment staus, attendance status and tags are searched, depending on prefixes given.

    +
  • +
  • +

    Only full words will be matched e.g. p/PAID will not match p/NOTPAID

    +
  • +
  • +

    Guests matching all keywords will be returned (i.e. AND search). e.g. pa/PAID t/GUEST will return a list +of people who have paid and who are guests

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    filter pa/NOTPAID a/PRESENT
    +You will be shown a list with guests who have yet to pay and are present.

    +
  • +
  • +

    filter a/Present t/Vegetarian
    +You will be shown a list with guests who are present and have a vegetarian dietary requirement.

    +
  • +
+
+
+
+

3.16. Sending emails to individual guests : email

+ +++ + + + + + +

Please ensure you have gone through Section 5, “Enabling Email Services” in order for this feature to work!

+
+

Sends an email to the guest at a specific Index
+Format: email INDEX

+
+
+
+
+
    +
  • +

    Sends an email to the guest at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed guest list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+ + +
+

The undo/redo feature will not work with the email command as once you have sent an email, you cannot reverse this action.

+
+
+
+
+

Examples:

+
+
+
    +
  • +

    email 2

    +
    +
      +
    1. +

      First, you will be presented with an EmailWindow similar to Figure 1 below. This window is for you to input your email address, password, email subject and message.

      +
    2. +
    3. +

      You will then need to fill in all the required fields. If you miss any of the fields and try click the Send button, an error message will pop up as the one in Figure 2.

      +
    4. +
    5. +

      Once all fields are filled, you can click the Send button to send your email to the 2nd guest in the list. You can also click the Quit button if you don’t want to send your email.

      +
    6. +
    +
    +
  • +
+
+ ++++ + + + + + + +

EmailWindow Figure 1 - EmailWindow

EmailWindowError Figure 2 - Field Errors

+
+
+

3.17. Sending an email to all guests : emailAll

+ +++ + + + + + +

Please ensure you have gone through Section 5, “Enabling Email Services” in order for this feature to work!

+
+

Sends an email to all of the guests in the current list
+Format: emailAll

+
+
+ + + + + +
+ + +
+

The undo/redo feature will not work with the email command as once you have sent an email, you cannot reverse this action.

+
+
+
+
+

Examples:

+
+
+
    +
  • +

    emailAll

    +
    +
      +
    1. +

      First, you will be presented with an EmailWindow similar to Figure 1 below. This window is for you to input your email address, password, email subject and message.

      +
    2. +
    3. +

      You will then need to fill in all the required fields. If you miss any of the fields and try click the Send button, an error message will pop up as the one in Figure 2.

      +
    4. +
    5. +

      Once all fields are filled, you can click the Send button to send your email to all guests in the list. You can also click the Quit button if you don’t want to send your email.

      +
    6. +
    +
    +
  • +
+
+ ++++ + + + + + + +

EmailWindow Figure 1 - EmailWindow

EmailWindowError Figure 2 - Field Errors

+
+
+

3.18. Sending emails to specified guests, ignoring potential spam : forceEmail - Coming in v2.0

+ +++ + + + + + +

Please ensure you have gone through Section 5, “Enabling Email Services” in order for this feature to work!

+
+

Sends an email to the guest specified by the index, regardless of how many emails have been sent to that guest
+Format: forceEmail INDEX

+
+
+
+
+
    +
  • +

    Sends an email to the guest at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed guest list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+ + +
+

The undo/redo feature will not work with the email command as once you have sent an email, you cannot reverse this action.

+
+
+

If you try to send multiple emails to the same guest, the system will inform you of this and not allow you to spam the guest. +However, the forceEmail command will allow you to send another email, regardless of how many emails have been sent before.

+
+
+
+
+
    +
  • +

    forceEmail
    +Forces an email to be sent to the guest specified by INDEX.

    +
    +
      +
    1. +

      First, you will be presented with an EmailWindow similar to Figure 1 below. This window is for you to input your email address, password, email subject and message.

      +
    2. +
    3. +

      You will then need to fill in all the required fields. If you miss any of the fields and try click the Send button, an error message will pop up as the one in Figure 2.

      +
    4. +
    5. +

      Once all fields are filled, you can click the Send button to send your email to all guests in the list. You can also click the Quit button if you don’t want to send your email.

      +
    6. +
    +
    +
  • +
+
+ ++++ + + + + + + +

EmailWindow Figure 1 - EmailWindow

EmailWindowError Figure 2 - Field Errors

+
+
+

3.19. Sending emails to specific groups of guests : emailSpecialised - Coming in v2.0

+ +++ + + + + + +

Please ensure you have gone through Section 5, “Enabling Email Services” in order for this feature to work!

+
+ + + + + +
+ + +
+

The undo/redo feature will not work with the email command as once you have sent an email, you cannot reverse this action.

+
+
+
+
+
+

3.20. Adding event details : add_event

+
+

Add the details for an event
+Format: add_event n/EVENT_NAME d/DATE v/VENUE st/START_TIME [t/OTHER_TAGS]

+
+
+
+
+
    +
  • +

    Adds the specified event details.

    +
  • +
  • +

    All compulsory fields(name, date, venue and start time) must be specified. The optional field(tags) may be omitted.

    +
  • +
  • +

    Event name and venue have to be alphanumeric and may contain spaces. Otherwise, the system will inform you about the correct format to be followed. Special characters like '#', ',' and '-' may be used for the venue field.

    +
  • +
  • +

    Event date has to follow the 'dd/mm/yyyy' format and has to exist in the calendar. Ensure that the event date falls after the current system date. Otherwise, the system will inform you about the correct format to be followed.

    +
  • +
  • +

    Event’s start time should follow the 'h:mm AM/PM' format with h between 1 to 12. Otherwise, the system will inform you about the correct format to be followed.

    +
  • +
  • +

    Event tags must be alphanumeric. Spaces are not allowed

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    add_event n/CFG career talk d/12/01/2019 v/YIH Paris Room st/9:00 AM t/SmartCasualAttire
    +You will create an event called CFG career talk that will take place on 12th January, 2019 at YIH Paris Room. The event will start at 9:00 AM and attendees are expected to dress in smart casual attire.

    +
  • +
+
+
+
+

3.21. Editing the event’s details : edit_event

+
+

Edit the details of the event
+Format: edit_event [n/EVENT_NAME] [d/DATE] [v/VENUE] [st/START_TIME] [t/…​]

+
+
+
+
+
    +
  • +

    Edits the event details(previously specified by the you using the add_event command).

    +
  • +
  • +

    Ensure that you have specified some event details before using this command. Otherwise, the system will inform you about the lack of event details.

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Existing values will be updated to the input values.

    +
  • +
  • +

    When editing tags, the existing tags of the event will be removed i.e adding of tags is not cumulative.

    +
  • +
  • +

    You can remove all event tags by typing t/ without specifying any tags after it.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    edit_event n/CFG Career Workshop t/CasualAttire
    +You will change the name of the event to 'CFG Career Workshop and replace the existing tags with the 'CasualAttire' tag.

    +
  • +
+
+
+
+

3.22. Deleting event details : delete_event

+
+

Deletes the event details currently present in the application +Format: delete_event

+
+
+
+
+
    +
  • +

    Deletes the event details(previously specified by you using the add_event command).

    +
  • +
  • +

    Ensure that you have specified some details before using this command. Otherwise, the system will inform you about the lack of event details.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    delete_event
    +You will delete the event details. +=== Undoing previous command : undo

    +
  • +
+
+
+

Restores the guest list to the state before the previous undoable command was executed.
+Format: undo

+
+
+ + + + + +
+ + +
+

Undoable commands: those commands that modify the guest list’s content (add_guest, delete_guest, edit_guest, removeTag, addTag, and clear).

+
+
+
+
+

Examples:

+
+
+
    +
  • +

    delete_guest 1
    +list
    +undo (reverses the delete_guest 1 command)

    +
  • +
  • +

    select 1
    +list
    +undo
    +The undo command fails as there are no undoable commands executed previously.

    +
  • +
  • +

    delete_guest 1
    +clear
    +undo (reverses the clear command)
    +undo (reverses the delete_guest 1 command)

    +
  • +
+
+
+
+

3.23. Redoing the previously undone command : redo

+
+

Reverses the most recent undo command.
+Format: redo

+
+
+

Examples:

+
+
+
    +
  • +

    delete_guest 1
    +undo (reverses the delete_guest 1 command)
    +redo (reapplies the delete_guest 1 command)

    +
  • +
  • +

    delete_guest 1
    +redo
    +The redo command fails as there are no undo commands executed previously.

    +
  • +
  • +

    delete_guest 1
    +clear
    +undo (reverses the clear command)
    +undo (reverses the delete_guest 1 command)
    +redo (reapplies the delete_guest 1 command)
    +redo (reapplies the clear command)

    +
  • +
+
+
+
+

3.24. Exiting the program : exit

+
+

Exits the program.
+Format: exit

+
+
+
+

3.25. Saving the data

+
+

As the guest list data are saved in the hard disk automatically after any command that changes the data.
+There is no need for you to save manually.

+
+
+
+
+
+

4. 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 guest list.
+Alternatively, you could export the data from your current computer using the export command. Using this data you can launch the application on a different computer and import the data.

+
+
+

Q: ​How do I import my existing data on a CSV into the application?
+A: Firstly, create a new event within the application. After this, use the import function and specify the path to the file. +You will see the guest list populate itself with the data from the specified CSV file.

+
+
+
+
+

5. Enabling Email Services

+
+
+

In order for you to use the commands email, emailAll, forceEmail, and emailSpecialised you must allow Invités to access your email account and +send emails. Currently, our application only supports Gmail accounts, but we do plan on supporting +other email domains.

+
+
+

If you do have a Gmail account, please follow these steps to enable email services:

+
+
+
    +
  1. +

    Login to your Gmail account using your preferred online browser (e.g. Chrome, Firefox).

    +
  2. +
  3. +

    Click on your profile picture on the top right, and click on Google Account

    +
  4. +
  5. +

    Once you are re-directed, under the Sign-in and security section, click on Apps with account access

    +
  6. +
  7. +

    Scroll down till you find the section Allow less secure apps on the right. Set this option to ON.

    +
  8. +
  9. +

    You are now ready to send emails to your guests through Invités!

    +
  10. +
+
+
+ + + + + +
+ + +Currently there is no other way to enable mailing services than to let your Gmail account allow less secure apps, and hence your account may be +susceptible to an increased number of spam emails from untrusted applications. However, our team is working quickly to find a more secure replacement. +
+
+ +++ + + + + + + + + + + + +

For testing purposes, you may use a default Gmail account we have created to save you some time:

Email Address: invitestestpe1@gmail.com

Password: practicalexam1

+
+
+
+

6. CSV Guest List Format

+
+
+

The import and export command will work with CSV files according to the predefined format below

+
+
+

Format: NAME,PHONE_NUMBER,EMAIL,PAYMENT,[TAG]

+
+
+

Example: sample CSV file

+
+
+
+
+

David Li,91031282,lidavid@gmail.com,PENDING,ABSENT,gold,Veg,VIP
+Irfan Ibrahim,92492021,irfan@gmail.com,PAID,PRESENT,gold,Veg,VIP
+Roy Balakrishnan,92624417,royb@gmail.com,PENDING,ABSENT,gold,Veg,VIP
+Hakeem Salgado,80000957,80000957@gmail.com,PAID,ABSENT,gold,Veg,VIP
+Hibah Lees,80000882,80000882@gmail.com,PAID,ABSENT,gold,Veg,VIP
+Jayda Hill,80000251,80000251@gmail.com,PAID,ABSENT,gold,Veg,VIP

+
+
+
+
+ + + + + +
+ + +
+

Individual guest fields shall not contain any commas.

+
+
+
+
+
+
+

7. Command Summary

+
+
+
    +
  • +

    help : ​Displays a help sheet containing useful commands.

    +
  • +
  • +

    add_guest : ​Creates an entry for a guest to attend the event.

    +
  • +
  • +

    edit_guest :​ Modifies the entry of a specified guest based on name.

    +
  • +
  • +

    delete_guest : ​Removes an entry of a specified guest based on name.

    +
  • +
  • +

    removeTag : Removes a set of tags from all the guests in the current list.

    +
  • +
  • +

    addtag : Adds a set of tags to all the guests in the current list.

    +
  • +
  • +

    find : Finds guests whose names, phone numbers or email addresses contain any of the given keywords.

    +
  • +
  • +

    list : Lists the current guest list.

    +
  • +
  • +

    import : ​Automatically generates guest list from a given CSV file.

    +
  • +
  • +

    export : Exports guest list to a CSV file.

    +
  • +
  • +

    mark : ​Tags a guest to note that they are currently at the event.

    +
  • +
  • +

    unmark : ​Removes the tag of a guest attending the event based on name.

    +
  • +
  • +

    start_marking : ​Begins continuous marking of the people entering the event.

    +
  • +
  • +

    stop_marking : ​Stops the continuous marking of people.

    +
  • +
  • +

    filter : ​Filters the guest list based on keywords given.

    +
  • +
  • +

    email : ​Sends individual emails to an entry of a specified guest based on index.

    +
  • +
  • +

    emailAll : Sends an email to all of the guests in the current list.

    +
  • +
  • +

    forceEmail : Sends an email to the guest specified by the index, regardless of how many emails have been sent to that guest.

    +
  • +
  • +

    emailSpecialised : Sends an email to the guests with the specified tags.

    +
  • +
  • +

    add_event :​ Adds the details of your event.

    +
  • +
  • +

    edit_event : Edits the details of the event.

    +
  • +
  • +

    delete_event : ​Removes the specified event details.

    +
  • +
  • +

    undo : Restores the guest list to the state before the previous undoable command was executed.

    +
  • +
  • +

    redo : Reverses the most recent undo command.

    +
  • +
  • +

    exit : ​Exits the application.

    +
  • +
+
+
+
diff --git a/src/main/docs/images/AddDeleteEventSequenceDiagram.png b/src/main/docs/images/AddDeleteEventSequenceDiagram.png new file mode 100644 index 000000000000..eac9d91685d4 Binary files /dev/null and b/src/main/docs/images/AddDeleteEventSequenceDiagram.png differ diff --git a/src/main/docs/images/AddTagRemoveTagSequenceDiagram.png b/src/main/docs/images/AddTagRemoveTagSequenceDiagram.png new file mode 100644 index 000000000000..c3ade216dda5 Binary files /dev/null and b/src/main/docs/images/AddTagRemoveTagSequenceDiagram.png differ diff --git a/src/main/docs/images/Architecture.png b/src/main/docs/images/Architecture.png new file mode 100644 index 000000000000..bdc789000f77 Binary files /dev/null and b/src/main/docs/images/Architecture.png differ diff --git a/src/main/docs/images/DeletePersonSdForLogic.png b/src/main/docs/images/DeletePersonSdForLogic.png new file mode 100644 index 000000000000..0462b9b7be6e Binary files /dev/null and b/src/main/docs/images/DeletePersonSdForLogic.png differ diff --git a/src/main/docs/images/EditEventSequenceDiagram.png b/src/main/docs/images/EditEventSequenceDiagram.png new file mode 100644 index 000000000000..501e403d6f05 Binary files /dev/null and b/src/main/docs/images/EditEventSequenceDiagram.png differ diff --git a/src/main/docs/images/EmailWindow.png b/src/main/docs/images/EmailWindow.png new file mode 100644 index 000000000000..7810ad95c306 Binary files /dev/null and b/src/main/docs/images/EmailWindow.png differ diff --git a/src/main/docs/images/EmailWindowError.png b/src/main/docs/images/EmailWindowError.png new file mode 100644 index 000000000000..fba040536dc7 Binary files /dev/null and b/src/main/docs/images/EmailWindowError.png differ diff --git a/src/main/docs/images/ExportCommandSequenceDiagram.PNG b/src/main/docs/images/ExportCommandSequenceDiagram.PNG new file mode 100644 index 000000000000..b62e93e28106 Binary files /dev/null and b/src/main/docs/images/ExportCommandSequenceDiagram.PNG differ diff --git a/src/main/docs/images/ExportSequenceRefFrame.PNG b/src/main/docs/images/ExportSequenceRefFrame.PNG new file mode 100644 index 000000000000..cd420d1680d4 Binary files /dev/null and b/src/main/docs/images/ExportSequenceRefFrame.PNG differ diff --git a/src/main/docs/images/FilterSequenceDiagram.png b/src/main/docs/images/FilterSequenceDiagram.png new file mode 100644 index 000000000000..af0596b0cd69 Binary files /dev/null and b/src/main/docs/images/FilterSequenceDiagram.png differ diff --git a/src/main/docs/images/FindSequenceDiagram.png b/src/main/docs/images/FindSequenceDiagram.png new file mode 100644 index 000000000000..1ed9f79f19de Binary files /dev/null and b/src/main/docs/images/FindSequenceDiagram.png differ diff --git a/src/main/docs/images/ImportCommandSequenceDiagram.PNG b/src/main/docs/images/ImportCommandSequenceDiagram.PNG new file mode 100644 index 000000000000..c90baf88ffc6 Binary files /dev/null and b/src/main/docs/images/ImportCommandSequenceDiagram.PNG differ diff --git a/src/main/docs/images/ImportExportClassDiagram.PNG b/src/main/docs/images/ImportExportClassDiagram.PNG new file mode 100644 index 000000000000..70b86beb32ea Binary files /dev/null and b/src/main/docs/images/ImportExportClassDiagram.PNG differ diff --git a/src/main/docs/images/ImportReportWindow.PNG b/src/main/docs/images/ImportReportWindow.PNG new file mode 100644 index 000000000000..ecdfed3d2cf2 Binary files /dev/null and b/src/main/docs/images/ImportReportWindow.PNG differ diff --git a/src/main/docs/images/ImportSequenceRefFrame.PNG b/src/main/docs/images/ImportSequenceRefFrame.PNG new file mode 100644 index 000000000000..775bbccfc522 Binary files /dev/null and b/src/main/docs/images/ImportSequenceRefFrame.PNG differ diff --git "a/src/main/docs/images/Invit\303\251sLogo.jpg" "b/src/main/docs/images/Invit\303\251sLogo.jpg" new file mode 100644 index 000000000000..144e2adf6b06 Binary files /dev/null and "b/src/main/docs/images/Invit\303\251sLogo.jpg" differ diff --git "a/src/main/docs/images/Invit\303\251sLogoBanner.png" "b/src/main/docs/images/Invit\303\251sLogoBanner.png" new file mode 100644 index 000000000000..01eb12446e2c Binary files /dev/null and "b/src/main/docs/images/Invit\303\251sLogoBanner.png" differ diff --git "a/src/main/docs/images/Invit\303\251sLogoSmall.png" "b/src/main/docs/images/Invit\303\251sLogoSmall.png" new file mode 100644 index 000000000000..97a5a18a21ed Binary files /dev/null and "b/src/main/docs/images/Invit\303\251sLogoSmall.png" differ diff --git "a/src/main/docs/images/Invit\303\251sLogoSmaller.png" "b/src/main/docs/images/Invit\303\251sLogoSmaller.png" new file mode 100644 index 000000000000..81169a650d6b Binary files /dev/null and "b/src/main/docs/images/Invit\303\251sLogoSmaller.png" differ diff --git a/src/main/docs/images/LogicClassDiagram.png b/src/main/docs/images/LogicClassDiagram.png new file mode 100644 index 000000000000..f4ecf65b3193 Binary files /dev/null and b/src/main/docs/images/LogicClassDiagram.png differ diff --git a/src/main/docs/images/MarkUnmarkEventSequenceDiagram.png b/src/main/docs/images/MarkUnmarkEventSequenceDiagram.png new file mode 100644 index 000000000000..0e45ce13f19b Binary files /dev/null and b/src/main/docs/images/MarkUnmarkEventSequenceDiagram.png differ diff --git a/src/main/docs/images/ModelClassBetterOopDiagram.png b/src/main/docs/images/ModelClassBetterOopDiagram.png new file mode 100644 index 000000000000..9ba8eb5e31d0 Binary files /dev/null and b/src/main/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/src/main/docs/images/ModelClassDiagram.png b/src/main/docs/images/ModelClassDiagram.png new file mode 100644 index 000000000000..9fb19078b859 Binary files /dev/null and b/src/main/docs/images/ModelClassDiagram.png differ diff --git a/src/main/docs/images/ModelComponentClassBetterOopDiagram.png b/src/main/docs/images/ModelComponentClassBetterOopDiagram.png new file mode 100644 index 000000000000..d588425fff70 Binary files /dev/null and b/src/main/docs/images/ModelComponentClassBetterOopDiagram.png differ diff --git a/src/main/docs/images/ModelComponentClassDiagram.png b/src/main/docs/images/ModelComponentClassDiagram.png new file mode 100644 index 000000000000..4885d0a3cab7 Binary files /dev/null and b/src/main/docs/images/ModelComponentClassDiagram.png differ diff --git a/src/main/docs/images/SDforDeletePerson.png b/src/main/docs/images/SDforDeletePerson.png new file mode 100644 index 000000000000..1e836f10dcd8 Binary files /dev/null and b/src/main/docs/images/SDforDeletePerson.png differ diff --git a/src/main/docs/images/SDforDeletePersonEventHandling.png b/src/main/docs/images/SDforDeletePersonEventHandling.png new file mode 100644 index 000000000000..ecec0805d32c Binary files /dev/null and b/src/main/docs/images/SDforDeletePersonEventHandling.png differ diff --git a/src/main/docs/images/StorageClassDiagram.png b/src/main/docs/images/StorageClassDiagram.png new file mode 100644 index 000000000000..7a4cd2700cbf Binary files /dev/null and b/src/main/docs/images/StorageClassDiagram.png differ diff --git a/src/main/docs/images/Ui.png b/src/main/docs/images/Ui.png new file mode 100644 index 000000000000..db5524f94392 Binary files /dev/null and b/src/main/docs/images/Ui.png differ diff --git a/src/main/docs/images/UiClassDiagram.png b/src/main/docs/images/UiClassDiagram.png new file mode 100644 index 000000000000..369469ef176e Binary files /dev/null and b/src/main/docs/images/UiClassDiagram.png differ diff --git a/src/main/docs/images/UndoRedoActivityDiagram.png b/src/main/docs/images/UndoRedoActivityDiagram.png new file mode 100644 index 000000000000..55e4138cc64f Binary files /dev/null and b/src/main/docs/images/UndoRedoActivityDiagram.png differ diff --git a/src/main/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/src/main/docs/images/UndoRedoExecuteUndoStateListDiagram.png new file mode 100644 index 000000000000..29c365d6b4a1 Binary files /dev/null and b/src/main/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/src/main/docs/images/UndoRedoNewCommand1StateListDiagram.png b/src/main/docs/images/UndoRedoNewCommand1StateListDiagram.png new file mode 100644 index 000000000000..76e661d62027 Binary files /dev/null and b/src/main/docs/images/UndoRedoNewCommand1StateListDiagram.png differ diff --git a/src/main/docs/images/UndoRedoNewCommand2StateListDiagram.png b/src/main/docs/images/UndoRedoNewCommand2StateListDiagram.png new file mode 100644 index 000000000000..adcb9aeadc51 Binary files /dev/null and b/src/main/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/src/main/docs/images/UndoRedoNewCommand3StateListDiagram.png b/src/main/docs/images/UndoRedoNewCommand3StateListDiagram.png new file mode 100644 index 000000000000..aac9c5fe05db Binary files /dev/null and b/src/main/docs/images/UndoRedoNewCommand3StateListDiagram.png differ diff --git a/src/main/docs/images/UndoRedoNewCommand4StateListDiagram.png b/src/main/docs/images/UndoRedoNewCommand4StateListDiagram.png new file mode 100644 index 000000000000..66a0a3b5f323 Binary files /dev/null and b/src/main/docs/images/UndoRedoNewCommand4StateListDiagram.png differ diff --git a/src/main/docs/images/UndoRedoSequenceDiagram.png b/src/main/docs/images/UndoRedoSequenceDiagram.png new file mode 100644 index 000000000000..5c9d5936f098 Binary files /dev/null and b/src/main/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/src/main/docs/images/UndoRedoStartingStateListDiagram.png b/src/main/docs/images/UndoRedoStartingStateListDiagram.png new file mode 100644 index 000000000000..002f3e2bbf79 Binary files /dev/null and b/src/main/docs/images/UndoRedoStartingStateListDiagram.png differ diff --git a/src/main/docs/images/aaryamnus.png b/src/main/docs/images/aaryamnus.png new file mode 100644 index 000000000000..3cb6b9b71ba0 Binary files /dev/null and b/src/main/docs/images/aaryamnus.png differ diff --git a/src/main/docs/images/build_pending.png b/src/main/docs/images/build_pending.png new file mode 100644 index 000000000000..2e2f0d4380ca Binary files /dev/null and b/src/main/docs/images/build_pending.png differ diff --git a/src/main/docs/images/checkstyle-idea-configuration.png b/src/main/docs/images/checkstyle-idea-configuration.png new file mode 100644 index 000000000000..d279d3a4e97c Binary files /dev/null and b/src/main/docs/images/checkstyle-idea-configuration.png differ diff --git a/src/main/docs/images/checkstyle-idea-scan-scope.png b/src/main/docs/images/checkstyle-idea-scan-scope.png new file mode 100644 index 000000000000..39a5b57b1186 Binary files /dev/null and b/src/main/docs/images/checkstyle-idea-scan-scope.png differ diff --git a/src/main/docs/images/chrome_save_as_pdf.png b/src/main/docs/images/chrome_save_as_pdf.png new file mode 100644 index 000000000000..53a1190bd48d Binary files /dev/null and b/src/main/docs/images/chrome_save_as_pdf.png differ diff --git a/src/main/docs/images/flick_repository_switch.png b/src/main/docs/images/flick_repository_switch.png new file mode 100644 index 000000000000..a6009dd44cdb Binary files /dev/null and b/src/main/docs/images/flick_repository_switch.png differ diff --git a/src/main/docs/images/generate_token.png b/src/main/docs/images/generate_token.png new file mode 100644 index 000000000000..aa8cee9f3bee Binary files /dev/null and b/src/main/docs/images/generate_token.png differ diff --git a/src/main/docs/images/getting-started-ui-result-after.png b/src/main/docs/images/getting-started-ui-result-after.png new file mode 100644 index 000000000000..92198515866c Binary files /dev/null and b/src/main/docs/images/getting-started-ui-result-after.png differ diff --git a/src/main/docs/images/getting-started-ui-result-before.png b/src/main/docs/images/getting-started-ui-result-before.png new file mode 100644 index 000000000000..e1c17d85f194 Binary files /dev/null and b/src/main/docs/images/getting-started-ui-result-before.png differ diff --git a/src/main/docs/images/getting-started-ui-status-after.png b/src/main/docs/images/getting-started-ui-status-after.png new file mode 100644 index 000000000000..5963b735b411 Binary files /dev/null and b/src/main/docs/images/getting-started-ui-status-after.png differ diff --git a/src/main/docs/images/getting-started-ui-status-before.png b/src/main/docs/images/getting-started-ui-status-before.png new file mode 100644 index 000000000000..7d3a38e4e45c Binary files /dev/null and b/src/main/docs/images/getting-started-ui-status-before.png differ diff --git a/src/main/docs/images/getting-started-ui-tag-after.png b/src/main/docs/images/getting-started-ui-tag-after.png new file mode 100644 index 000000000000..6dc47a6ca708 Binary files /dev/null and b/src/main/docs/images/getting-started-ui-tag-after.png differ diff --git a/src/main/docs/images/getting-started-ui-tag-before.png b/src/main/docs/images/getting-started-ui-tag-before.png new file mode 100644 index 000000000000..b00c70b0933b Binary files /dev/null and b/src/main/docs/images/getting-started-ui-tag-before.png differ diff --git a/src/main/docs/images/github_repo_settings.png b/src/main/docs/images/github_repo_settings.png new file mode 100644 index 000000000000..101d6e9d5623 Binary files /dev/null and b/src/main/docs/images/github_repo_settings.png differ diff --git a/src/main/docs/images/grant_access.png b/src/main/docs/images/grant_access.png new file mode 100644 index 000000000000..beb4c0ddc8b0 Binary files /dev/null and b/src/main/docs/images/grant_access.png differ diff --git a/src/main/docs/images/kronicler.png b/src/main/docs/images/kronicler.png new file mode 100644 index 000000000000..4989b8d7e533 Binary files /dev/null and b/src/main/docs/images/kronicler.png differ diff --git a/src/main/docs/images/request_access.png b/src/main/docs/images/request_access.png new file mode 100644 index 000000000000..12e8a81bd28f Binary files /dev/null and b/src/main/docs/images/request_access.png differ diff --git a/src/main/docs/images/review_and_add.png b/src/main/docs/images/review_and_add.png new file mode 100644 index 000000000000..81d60a36e885 Binary files /dev/null and b/src/main/docs/images/review_and_add.png differ diff --git a/src/main/docs/images/sandhyagopakumar.png b/src/main/docs/images/sandhyagopakumar.png new file mode 100644 index 000000000000..17502ed7e2c9 Binary files /dev/null and b/src/main/docs/images/sandhyagopakumar.png differ diff --git a/src/main/docs/images/sarahtaaherbonna.png b/src/main/docs/images/sarahtaaherbonna.png new file mode 100644 index 000000000000..cd0ae7c7f1c2 Binary files /dev/null and b/src/main/docs/images/sarahtaaherbonna.png differ diff --git a/src/main/docs/images/signing_in.png b/src/main/docs/images/signing_in.png new file mode 100644 index 000000000000..6d1ad4fe26f3 Binary files /dev/null and b/src/main/docs/images/signing_in.png differ diff --git a/src/main/docs/images/travis_add_token.png b/src/main/docs/images/travis_add_token.png new file mode 100644 index 000000000000..06e4dd075faf Binary files /dev/null and b/src/main/docs/images/travis_add_token.png differ diff --git a/src/main/docs/images/travis_build.png b/src/main/docs/images/travis_build.png new file mode 100644 index 000000000000..0c4061bc0e23 Binary files /dev/null and b/src/main/docs/images/travis_build.png differ diff --git a/src/main/docs/images/wm28.png b/src/main/docs/images/wm28.png new file mode 100644 index 000000000000..90f62b6d05d8 Binary files /dev/null and b/src/main/docs/images/wm28.png differ diff --git a/src/main/docs/stylesheets/asciidoctor.css b/src/main/docs/stylesheets/asciidoctor.css new file mode 100644 index 000000000000..36590bf346cd --- /dev/null +++ b/src/main/docs/stylesheets/asciidoctor.css @@ -0,0 +1,407 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Remove comment around @import statement below when using as a custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +body{margin:0} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box} +input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +body{-webkit-font-smoothing:antialiased} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.spread{width:100%} +p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ul.no-bullet{list-style:none} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite:before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7;font-weight:bold} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +body{tab-size:4} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table} +.clearfix:after,.float-group:after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menu{color:rgba(0,0,0,.8)} +b.button:before,b.button:after{position:relative;top:-1px;font-weight:400} +b.button:before{content:"[";padding:0 3px 0 2px} +b.button:after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table} +#header:after,#content:after,#footnotes:after,#footer:after{clear:both} +#content{margin-top:1.25em} +#content:before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px} +#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span:before{content:"\00a0\2013\00a0"} +#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark:before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber:after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #efefed;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media only screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}} +@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +.sect1{padding-bottom:.625em} +@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}} +.sect1+.sect1{border-top:1px solid #efefed} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0} +.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em} +.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal} +@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]:before{display:block} +.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right} +.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)} +.quoteblock .quoteblock blockquote{padding:0 0 0 .75em} +.quoteblock .quoteblock blockquote:before{display:none} +.verseblock{margin:0 1em 1.25em 1em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract{margin:0 0 1.25em 0;display:block} +.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0} +.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none} +table.tableblock{max-width:100%;border-collapse:separate} +table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0} +table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0} +table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0} +table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0} +table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0} +table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0} +table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot{border-width:1px 0} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none} +ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1em;font-size:.85em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{width:1em;position:relative;top:1px} +ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden} +ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block} +ul.inline>li>*{display:block} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1} +.colist>table tr>td:last-of-type{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0} +.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]:after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@media print{@page{margin:1.25cm .75cm} +*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]:after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important} +.sect1{padding-bottom:0!important} +.sect1+.sect1{border:0!important} +#header>h1:first-child{margin-top:1.25rem} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span:before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]:before{display:block} +#footer{background:none!important;padding:0 .9375em} +#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} diff --git a/src/main/docs/stylesheets/coderay-asciidoctor.css b/src/main/docs/stylesheets/coderay-asciidoctor.css new file mode 100644 index 000000000000..ad53f53dc999 --- /dev/null +++ b/src/main/docs/stylesheets/coderay-asciidoctor.css @@ -0,0 +1,89 @@ +/* Stylesheet for CodeRay to match GitHub theme | MIT License | http://foundation.zurb.com */ +/*pre.CodeRay {background-color:#f7f7f8;}*/ +.CodeRay .line-numbers{border-right:1px solid #d8d8d8;padding:0 0.5em 0 .25em} +.CodeRay span.line-numbers{display:inline-block;margin-right:.5em;color:rgba(0,0,0,.3)} +.CodeRay .line-numbers strong{color:rgba(0,0,0,.4)} +table.CodeRay{border-collapse:separate;border-spacing:0;margin-bottom:0;border:0;background:none} +table.CodeRay td{vertical-align: top;line-height:1.45} +table.CodeRay td.line-numbers{text-align:right} +table.CodeRay td.line-numbers>pre{padding:0;color:rgba(0,0,0,.3)} +table.CodeRay td.code{padding:0 0 0 .5em} +table.CodeRay td.code>pre{padding:0} +.CodeRay .debug{color:#fff !important;background:#000080 !important} +.CodeRay .annotation{color:#007} +.CodeRay .attribute-name{color:#000080} +.CodeRay .attribute-value{color:#700} +.CodeRay .binary{color:#509} +.CodeRay .comment{color:#998;font-style:italic} +.CodeRay .char{color:#04d} +.CodeRay .char .content{color:#04d} +.CodeRay .char .delimiter{color:#039} +.CodeRay .class{color:#458;font-weight:bold} +.CodeRay .complex{color:#a08} +.CodeRay .constant,.CodeRay .predefined-constant{color:#008080} +.CodeRay .color{color:#099} +.CodeRay .class-variable{color:#369} +.CodeRay .decorator{color:#b0b} +.CodeRay .definition{color:#099} +.CodeRay .delimiter{color:#000} +.CodeRay .doc{color:#970} +.CodeRay .doctype{color:#34b} +.CodeRay .doc-string{color:#d42} +.CodeRay .escape{color:#666} +.CodeRay .entity{color:#800} +.CodeRay .error{color:#808} +.CodeRay .exception{color:inherit} +.CodeRay .filename{color:#099} +.CodeRay .function{color:#900;font-weight:bold} +.CodeRay .global-variable{color:#008080} +.CodeRay .hex{color:#058} +.CodeRay .integer,.CodeRay .float{color:#099} +.CodeRay .include{color:#555} +.CodeRay .inline{color:#000} +.CodeRay .inline .inline{background:#ccc} +.CodeRay .inline .inline .inline{background:#bbb} +.CodeRay .inline .inline-delimiter{color:#d14} +.CodeRay .inline-delimiter{color:#d14} +.CodeRay .important{color:#555;font-weight:bold} +.CodeRay .interpreted{color:#b2b} +.CodeRay .instance-variable{color:#008080} +.CodeRay .label{color:#970} +.CodeRay .local-variable{color:#963} +.CodeRay .octal{color:#40e} +.CodeRay .predefined{color:#369} +.CodeRay .preprocessor{color:#579} +.CodeRay .pseudo-class{color:#555} +.CodeRay .directive{font-weight:bold} +.CodeRay .type{font-weight:bold} +.CodeRay .predefined-type{color:inherit} +.CodeRay .reserved,.CodeRay .keyword {color:#000;font-weight:bold} +.CodeRay .key{color:#808} +.CodeRay .key .delimiter{color:#606} +.CodeRay .key .char{color:#80f} +.CodeRay .value{color:#088} +.CodeRay .regexp .delimiter{color:#808} +.CodeRay .regexp .content{color:#808} +.CodeRay .regexp .modifier{color:#808} +.CodeRay .regexp .char{color:#d14} +.CodeRay .regexp .function{color:#404;font-weight:bold} +.CodeRay .string{color:#d20} +.CodeRay .string .string .string{background:#ffd0d0} +.CodeRay .string .content{color:#d14} +.CodeRay .string .char{color:#d14} +.CodeRay .string .delimiter{color:#d14} +.CodeRay .shell{color:#d14} +.CodeRay .shell .delimiter{color:#d14} +.CodeRay .symbol{color:#990073} +.CodeRay .symbol .content{color:#a60} +.CodeRay .symbol .delimiter{color:#630} +.CodeRay .tag{color:#008080} +.CodeRay .tag-special{color:#d70} +.CodeRay .variable{color:#036} +.CodeRay .insert{background:#afa} +.CodeRay .delete{background:#faa} +.CodeRay .change{color:#aaf;background:#007} +.CodeRay .head{color:#f8f;background:#505} +.CodeRay .insert .insert{color:#080} +.CodeRay .delete .delete{color:#800} +.CodeRay .change .change{color:#66f} +.CodeRay .head .head{color:#f4f} diff --git a/src/main/docs/stylesheets/gh-pages.css b/src/main/docs/stylesheets/gh-pages.css new file mode 100644 index 000000000000..121cac3885fd --- /dev/null +++ b/src/main/docs/stylesheets/gh-pages.css @@ -0,0 +1,214 @@ +@import url(https://fonts.googleapis.com/css?family=Montserrat|Open+Sans); +@import "asciidoctor.css"; /* Default asciidoc style framework - important */ + +/* Custom block: details */ + +.sidebarblock.details > .content { + border-left: .25rem solid rgba(0, 0, 0, 0.1); +} + +.sidebarblock.details > .content { + padding-left: .5rem +} + +.sidebarblock.details { + background-color: transparent; + border: none; + padding-bottom: 0; + padding-top: 0; +} + +/* Overrides for asciidoctor.css */ + +a { + color: #0074c7; +} + +h1, +#content h1 > a.link, +h2, +h2 > a.link, +h3, +h3 > a.link, +#toctitle, +#toctitle > a.link, +.sidebarblock > .content > .title, +.sidebarblock > .content > .title > a.link, +h4, +h4 > a.link, +h5, +h5 > a.link, +h6, +h6 > a.link { + color: #e46c0a; +} + +.subheader, +.admonitionblock td.content > .title, +.audioblock > .title, +.exampleblock > .title, +.imageblock > .title, +.listingblock > .title, +.literalblock > .title, +.stemblock > .title, +.openblock > .title, +.paragraph >.title, +.quoteblock > .title, +table.tableblock > .title, +.verseblock > .title, +.videoblock > .title, +.dlist > .title, +.olist > .title, +.ulist > .title, +.qlist > .title, +.hdlist > .title { + color: rgb(197, 90, 17); +} + +@media screen { + #footer { + background-color: #f6f6f6; + border-top: 1px #d2d2d2 solid; + border-bottom: 1px #d2d2d2 solid; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + } + + #footer-text { + color: #595959; + line-height: 1; + } +} + +/* Utilities */ + +.container { + width: 100%; + max-width: 62.5rem; + margin-left: auto; + margin-right: auto; +} + +/* Colors */ + +.bg-light { + background-color: #f8f9fa; +} + +.bg-lighter { + background-color: #fbfbfb; +} + + +/* Navbar */ + +.navbar { + display: flex; + flex-wrap: nowrap; + justify-content: center; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + font-size: 1rem; + padding: 0px 1rem; +} + +.navbar-lg { + font-size: 1.3rem; +} + +.navbar-light { + border-bottom: 1px #d2d2d2 solid; +} + +.navbar a { + text-decoration: none; +} + +.navbar-light a { + color: #595959; +} + +.navbar-light a:hover, +.navbar-light a:focus { + color: #000000; +} + +.navbar a.active, +.navbar a.active:hover, +.navbar a.active:focus { + font-weight: bold; +} + +.navbar-light a.active, +.navbar-light a.active:hover, +.navbar-light a.active:focus { + color: #000000; +} + +.navbar-light .nav-link { + border-bottom: 2px transparent solid; +} + +.navbar-light .nav-link.active { + border-bottom: 2px #e46c0a solid; +} + +.navbar-lg .nav-link.active { + border-bottom: 0; +} + +.navbar > .container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.navbar-brand { + display: inline-block; + margin-right: 1rem; + padding: 0.8125rem 0rem; + padding-left: 0.9375rem; + font-size: 1.25rem; +} + +.navbar-brand img { + height: 1.4rem; + margin: 0rem 0.4rem; + padding: 0; + vertical-align: middle; +} + +.navbar-lg .navbar-brand { + font-size: 1.7rem; +} + +.navbar-lg .navbar-brand img { + height: 2.3rem; +} + +.navbar-nav { + display: flex; + flex-wrap: wrap; + flex-grow: 1; + align-items: center; + margin: 0px; + padding: 0px; + list-style: none; + line-height: inherit; +} + +.nav-link { + display: block; + margin: 0px; + border: 0px; + padding: 1rem 1rem; +} + +/* Do not display site header on print mediums */ +@media print { + #seedu-header { + display: none; + } + + #site-header { + display: none; + } +} diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..ddb4146b0a5a 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,11 +36,11 @@ import seedu.address.ui.UiManager; /** - * The main entry point to the application. + * This is 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, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -51,7 +51,6 @@ public class MainApp extends Application { protected Config config; protected UserPrefs userPrefs; - @Override public void init() throws Exception { logger.info("=============================[ Initializing AddressBook ]==========================="); diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..b7ebb11c7fab 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 = "Invités"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..b410e41a8f1c 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,31 @@ 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_PERSON_DISPLAYED_INDEX = "The person index provided is invalid!"; + public static final String MESSAGE_EMPTY_LIST = "There are no guests in the current list to send emails to!"; + public static final String MESSAGE_UNSUPPORTED_FILE_EXTENSION = "Invalid file extension! \n%1$s"; + public static final String MESSAGE_INVALID_FILE_PATH = "Invalid file path! \n%1$s"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_FILE_NOT_FOUND = "%1$s does not exists!"; + public static final String MESSAGE_FILE_ALREADY_EXIST = "%1$s already exists!"; + public static final String MESSAGE_EDITING_UID = "ERROR: A guest's UID cannot be edited"; + + //@@author aaryamNUS + public static final String MESSAGE_USERNAME_NOT_PROVIDED = "Error: you have not provided your username " + + "for authentication!"; + public static final String MESSAGE_PASSWORD_NOT_PROVIDED = "Error: you have not provided your password " + + "for authentication!"; + public static final String MESSAGE_EMAIL_SUBJECT_NOT_PROVIDED = "Error: you have not included the subject " + + "of your email!"; + public static final String MESSAGE_EMAIL_MESSAGE_NOT_PROVIDED = "Error: you have not included the message " + + "of your email!"; + public static final String MESSAGE_NO_EMAIL_SENT_MESSAGE = "No emails sent to any guests!"; + public static final String MESSAGE_INVALID_EMAIL = "Error: The email address you have provided is invalid!"; + public static final String MESSAGE_NO_INTERNET_CONNECTION_OR_INVALID_CREDENTIALS = "Error: could not send " + + "email, either your " + + "internet connection is not strong or the " + + "credentials provided are invalid!"; + public static final String MESSAGE_NO_PERSONS_WITH_TAGS = "Error: no guests in the current list have the " + + "specified tags!"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java index b72ad4740e5a..3ffd2b96e06c 100644 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java @@ -2,6 +2,7 @@ import seedu.address.commons.events.BaseEvent; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; /** Indicates the AddressBook in the model has changed*/ public class AddressBookChangedEvent extends BaseEvent { @@ -16,4 +17,8 @@ public AddressBookChangedEvent(ReadOnlyAddressBook data) { public String toString() { return "number of persons " + data.getPersonList().size(); } + + public Event getNewDetails() { + return this.data.getEventDetails(); + } } diff --git a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java index a5e8b2e13883..cd35dcff9b64 100644 --- a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java @@ -8,9 +8,11 @@ public class NewResultAvailableEvent extends BaseEvent { public final String message; + public final boolean isCorrectCommand; - public NewResultAvailableEvent(String message) { + public NewResultAvailableEvent(String message, boolean isCorrectCommand) { this.message = message; + this.isCorrectCommand = isCorrectCommand; } @Override diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionClearedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionClearedEvent.java new file mode 100644 index 000000000000..4d327a63a98c --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionClearedEvent.java @@ -0,0 +1,16 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +//@@author wm28 +/** + * Indicates that selection is cleared in the Person List Panel + */ +public class PersonPanelSelectionClearedEvent extends BaseEvent { + @Override + public String toString() { + return getClass().getSimpleName(); + } +} +//@@author + diff --git a/src/main/java/seedu/address/commons/events/ui/ShowImportReportEvent.java b/src/main/java/seedu/address/commons/events/ui/ShowImportReportEvent.java new file mode 100644 index 000000000000..ff1cddda9c0d --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ShowImportReportEvent.java @@ -0,0 +1,23 @@ +package seedu.address.commons.events.ui; + +import java.util.List; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.logic.converters.error.ImportError; + +/** + * An event requesting to show the import report + */ +public class ShowImportReportEvent extends BaseEvent { + public final List errors; + + public ShowImportReportEvent(List errors) { + assert errors != null : "ImportError list cannot be null"; + this.errors = errors; + } + + @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..ec0c866885c5 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -31,6 +31,21 @@ public static boolean isValidPath(String path) { return true; } + /** + * Returns true if {@code path} has the file extension, {@code extension} + * + * @param path A string representing the file path. Cannot be null. + * @param extension A string representing the file extension type. Cannot be null. + */ + public static boolean isValidFileExtension(String path, String extension) { + extension = extension.toLowerCase().trim(); + path = path.toLowerCase(); + if (!path.endsWith("." + extension)) { + return false; + } + return true; + } + /** * Creates a file if it does not exist along with its missing parent directories. * @throws IOException if the file or directory cannot be created. diff --git a/src/main/java/seedu/address/commons/util/QrUtil.java b/src/main/java/seedu/address/commons/util/QrUtil.java new file mode 100644 index 000000000000..38a15a01f3b8 --- /dev/null +++ b/src/main/java/seedu/address/commons/util/QrUtil.java @@ -0,0 +1,54 @@ +package seedu.address.commons.util; + +import java.awt.image.BufferedImage; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import javafx.scene.paint.Color; + +/** + * Helper functions for generating QR codes. + */ +public class QrUtil { + + private static final int MAX_DATA_SIZE = 100; + private static final int QR_SIZE = 256; + + /** + * Generates a QR code based on the input string. + * @param data A string representing the data to be encoded into a QR code + * @return The encoded QR code + * @throws WriterException if length of {@code data} is greater than MAX_DATA_SIZE or when {@code data} fails to + * encode + */ + public BufferedImage generateQr(String data) throws WriterException { + if (data.length() > MAX_DATA_SIZE) { + throw new WriterException("Data size too large"); + } + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, QR_SIZE, QR_SIZE); + + //Solution below adapted from https://docs.oracle.com/javafx/2/image_ops/jfxpub-image_ops.htm + WritableImage writableImage = new WritableImage(QR_SIZE, QR_SIZE); + PixelWriter pixelWriter = writableImage.getPixelWriter(); + for (int i = 0; i < bitMatrix.getHeight(); i++) { + for (int j = 0; j < bitMatrix.getWidth(); j++) { + if (bitMatrix.get(i, j)) { + pixelWriter.setColor(i, j, Color.BLACK); + } else { + pixelWriter.setColor(i, j, Color.WHITE); + } + } + } + //@@author {wm28}-reused + // Reused from https://community.oracle.com/thread/2450090?tstart=0 with minor modifications + return SwingFXUtils.fromFXImage(writableImage, null); + //@@author + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..6f4cd0157c60 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,8 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + Event getEventDetails(); + /** 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..ece08a9c13c1 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,6 +11,7 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -45,6 +46,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public Event getEventDetails() { + return model.getEventDetails(); + } + @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..35d98cc88195 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,43 +1,59 @@ 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_ATTENDANCE; 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_PAYMENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; + +import java.util.Random; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; /** * Adds a person to the address book. */ public class AddCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "add_guest"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a guest to the address book. \n" + + "Note: UID is either auto-assigned [if you enter u/00000] " + + "or user defined [anything other than u/00000].\n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_PAYMENT + "PAYMENT " + + PREFIX_ATTENDANCE + "ATTENDANCE " + + PREFIX_UID + "UID " + "[" + 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_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_EMAIL + "johnd@gmail.com " + + PREFIX_PAYMENT + "PAID " + + PREFIX_ATTENDANCE + "PRESENT " + + PREFIX_UID + "00000 " + + PREFIX_TAG + "NORMAL " + + PREFIX_TAG + "NoShrimp " + + PREFIX_TAG + "NORMAL"; + - 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 guest added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This guest already exists in the guest list."; + public static final String MESSAGE_DUPLICATE_UID = + "This UID is already used in the guest list. Please use another UID."; + public static final Uid DEFAULT_TO_GENERATE_UID = new Uid("00000"); - private final Person toAdd; + private Person toAdd; /** * Creates an AddCommand to add the specified {@code Person} @@ -47,6 +63,15 @@ public AddCommand(Person person) { toAdd = person; } + /** + * Generate a random UID + */ + public Uid generateUid() { + Random rnd = new Random(); + int number = rnd.nextInt(999999); + return new Uid(String.format("%06d", number)); + } + @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); @@ -55,6 +80,24 @@ public CommandResult execute(Model model, CommandHistory history) throws Command throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + //@@author kronicler + if (model.hasUid(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_UID); + } + + if (toAdd.getUid().equals(DEFAULT_TO_GENERATE_UID)) { + boolean unique = false; + while (!unique) { + Person temp = new Person(toAdd.getName(), toAdd.getPhone(), toAdd.getEmail(), toAdd.getPayment(), + toAdd.getAttendance(), generateUid(), toAdd.getTags()); + if (model.hasUid(temp) == false) { + unique = true; + toAdd = temp; + } + } + } + //@@author + model.addPerson(toAdd); model.commitAddressBook(); return new CommandResult(String.format(MESSAGE_SUCCESS, 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..2d3a2312b0ad --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddEventCommand.java @@ -0,0 +1,60 @@ +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +//@@author SandhyaGopakumar +/** + * Adds an event to the application. + */ +public class AddEventCommand extends Command { + public static final String COMMAND_WORD = "add_event"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an event to the application. " + + "Parameters: " + + PREFIX_NAME + "NAME " + " " + + PREFIX_DATE + "DATE" + " " + + PREFIX_VENUE + "VENUE" + " " + + PREFIX_START_TIME + "START TIME" + " " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Wedding " + " " + + PREFIX_DATE + "10/10/2019 " + " " + + PREFIX_VENUE + "Mandarin Hotel, 5th floor, Room 1A" + " " + + PREFIX_START_TIME + "10:00 AM" + " " + + PREFIX_TAG + "ClassicTheme"; + public static final String MESSAGE_SUCCESS = "New event added: %1$s"; + public static final String MESSAGE_DUPLICATE_EVENT = "An event already exists in the application"; + private final Event toAdd; + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public AddEventCommand(Event event) { + requireNonNull(event); + toAdd = event; + } + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.hasEvent()) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); + } + model.addEvent(toAdd); + model.commitAddressBook(); + 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/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java new file mode 100644 index 000000000000..710dfc07d72a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java @@ -0,0 +1,115 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Adds a set of tags from all the people in the current GuestBook + */ +public class AddTagCommand extends Command { + public static final String COMMAND_WORD = "addTag"; + public static final String MESSAGE_NO_PERSON_IN_LIST = "No persons in the list!"; + public static final String MESSAGE_NO_PERSON_TO_ADD_TAG = "All existing persons in the list " + + "already have the specified tags!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the specified tags from all " + + "persons in the list.\n" + + "Parameters: " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TAG + "VIP " + PREFIX_TAG + "Paid"; + static final String MESSAGE_ADDED_TAG_SUCCESS = "Successfully added all tags to %1$d persons"; + private static Logger logger = Logger.getLogger("execute"); + private final Set tagsToAdd; + private int numberOfPeopleToAddTags = 0; + + /** + * @param tagsToAdd of the person in the filtered person list to edit + */ + public AddTagCommand(Set tagsToAdd) { + requireNonNull(tagsToAdd); + this.tagsToAdd = tagsToAdd; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + ReadOnlyAddressBook currentAddressBookReadOnly = model.getAddressBook(); + + // Uses edited AddressBook Model to make an editable AddressBook for addTag() to work + AddressBook currentAddressBook = new AddressBook(currentAddressBookReadOnly); + List currentList = model.getFilteredPersonList(); + + if (currentList.isEmpty()) { + throw new CommandException(MESSAGE_NO_PERSON_IN_LIST); + } else { + if (calculateNumberOfPeopleToAddTags(currentList) == 0) { + throw new CommandException(MESSAGE_NO_PERSON_TO_ADD_TAG); + } + + for (Tag tagToBeAdded: tagsToAdd) { + currentAddressBook.addTag(tagToBeAdded); + } + logger.log(Level.INFO, "All tags added successfully"); + model.resetData(currentAddressBook); + model.commitAddressBook(); + + return new CommandResult(String.format(MESSAGE_ADDED_TAG_SUCCESS, numberOfPeopleToAddTags)); + } + } + + /** + * Calculates how many people in the list already have all of the tags specified + * @param currentList the current list of guests + */ + private int calculateNumberOfPeopleToAddTags(List currentList) { + assert numberOfPeopleToAddTags == 0 : "Number of people to add tags to should be 0 initially!"; + + Set currentTags; + + for (Person personToBeEdited : currentList) { + currentTags = personToBeEdited.getTags(); + for (Tag tagsToBeAdded: tagsToAdd) { + try { + if (!currentTags.contains(tagsToBeAdded)) { + numberOfPeopleToAddTags++; + break; + } + } catch (IllegalArgumentException ex) { + logger.log(Level.WARNING, "Incorrect format for tags", ex); + } + } + } + + return numberOfPeopleToAddTags; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof AddTagCommand)) { + return false; + } + // state check + AddTagCommand e = (AddTagCommand) other; + return tagsToAdd.equals(e.tagsToAdd); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..78911da1f172 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -13,7 +13,11 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Clears the current Guestlist.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example of usage: " + + COMMAND_WORD; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index a20e9d49eac7..5b661f97eeff 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -16,14 +16,14 @@ */ public class DeleteCommand extends Command { - public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_WORD = "delete_guest"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the guest identified by the index number used in the displayed guest list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Guest: %1$s"; private final Index targetIndex; 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..7e86c8306c6b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java @@ -0,0 +1,44 @@ +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 SandhyaGopakumar +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteEventCommand extends Command { + + public static final String COMMAND_WORD = "delete_event"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the event in the addressbook currently.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Deleted event details."; + public static final String MESSAGE_NO_EVENT_DETAILS = "Event details have not been put in yet."; + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (!model.hasEvent()) { + throw new CommandException(MESSAGE_NO_EVENT_DETAILS); + } + + model.deleteEvent(); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_DELETE_EVENT_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteEventCommand); // 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..46e3e5d78a5c 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,13 @@ 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_ATTENDANCE; 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_PAYMENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -20,11 +22,13 @@ import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; +import seedu.address.model.person.Attendance; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -32,24 +36,26 @@ */ public class EditCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "edit_guest"; - 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. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the guest identified " + + "by the index number used in the displayed guest list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_PAYMENT + "PAYMENT] " + + "[" + PREFIX_ATTENDANCE + "ATTENDANCE] " + + "[" + PREFIX_UID + "UID] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_EMAIL + "johndoe@gmail.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Guest: %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 guest already exists in the guest list."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -98,10 +104,13 @@ 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()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Payment updatedPayment = editPersonDescriptor.getPayment().orElse(personToEdit.getPayment()); + Attendance updatedAttendance = editPersonDescriptor.getAttendance().orElse(personToEdit.getAttendance()); + Uid updatedUid = personToEdit.getUid(); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedPayment, + updatedAttendance, updatedUid, updatedTags); } @Override @@ -130,7 +139,8 @@ public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private Payment payment; + private Attendance attendance; private Set tags; public EditPersonDescriptor() {} @@ -143,7 +153,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setPayment(toCopy.payment); + setAttendance(toCopy.attendance); setTags(toCopy.tags); } @@ -151,7 +162,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, payment, + attendance, tags); } public void setName(Name name) { @@ -170,20 +182,30 @@ public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + //@@author Sarah + public void setPayment(Payment payment) { + this.payment = payment; } - public Optional getEmail() { - return Optional.ofNullable(email); + public Optional getPayment() { + return Optional.ofNullable(payment); + } + + public void setAttendance(Attendance attendance) { + this.attendance = attendance; } - public void setAddress(Address address) { - this.address = address; + public Optional getAttendance() { + return Optional.ofNullable(attendance); } - public Optional
getAddress() { - return Optional.ofNullable(address); + //@@author + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); } /** @@ -221,7 +243,8 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) + && getPayment().equals(e.getPayment()) + && getAttendance().equals(e.getAttendance()) && 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..155b1fa4c9de --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditEventCommand.java @@ -0,0 +1,223 @@ +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +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.Event; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.EventStartTime; +import seedu.address.model.event.EventVenue; + +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing person in the address book. + */ +public class EditEventCommand extends Command { + + public static final String COMMAND_WORD = "edit_event"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the event details specified. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "EVENT NAME] " + + "[" + PREFIX_DATE + "EVENT DATE] " + + "[" + PREFIX_VENUE + "VENUE] " + + "[" + PREFIX_START_TIME + "START TIME] " + + "[" + PREFIX_TAG + "EVENT TAGS]...\n" + + "Example: " + COMMAND_WORD + + PREFIX_DATE + "12/01/2019 " + + PREFIX_VENUE + "SRC-RM2"; + + public static final String MESSAGE_EDIT_EVENT_SUCCESS = "Edited event details: %1$s"; + public static final String MESSAGE_NOT_EDITED_EVENT = "At least one field to edit must be provided."; + public static final String MESSAGE_NO_DETAILS = "No details about the event have been put in."; + public static final String MESSAGE_SAME_EVENT = "These event details are already present."; + + private final EditEventDetails editEventDetails; + + /** + * @param editEventDetails details to edit the person with + */ + public EditEventCommand(EditEventDetails editEventDetails) { + requireNonNull(editEventDetails); + this.editEventDetails = new EditEventDetails(editEventDetails); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + Event existingEvent = model.getEventDetails(); + + if (!model.hasEvent()) { + throw new CommandException(MESSAGE_NO_DETAILS); + } + + Event editedEvent = createEditedEvent(existingEvent, editEventDetails); + + if (existingEvent.isSameEvent(editedEvent)) { + throw new CommandException(MESSAGE_SAME_EVENT); + } + + model.updateEvent(editedEvent); + model.commitAddressBook(); + return new CommandResult(String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent)); + } + + /** + * Creates and returns a {@code Event} with the details of {@code existingEvent} + * edited with {@code editEventDetails}. + */ + private static Event createEditedEvent(Event existingEvent, EditEventDetails editEventDetails) { + assert existingEvent != null; + + EventName updatedEventName = editEventDetails.getEventName().orElse( + new EventName(existingEvent.getName())); + EventDate updatedEventDate = editEventDetails.getEventDate().orElse( + new EventDate(existingEvent.getDate())); + EventVenue updatedEventVenue = editEventDetails.getEventVenue().orElse( + new EventVenue(existingEvent.getVenue())); + EventStartTime updatedEventStartTime = editEventDetails.getEventStartTime() + .orElse(new EventStartTime(existingEvent.getStartTime())); + Set updatedEventTags = editEventDetails.getEventTags() + .orElse(existingEvent.getEventTags()); + Boolean eventIsNotInitialisedByUser = !existingEvent.isUserInitialised(); + + return new Event(updatedEventName, updatedEventDate, updatedEventVenue, + updatedEventStartTime, updatedEventTags, eventIsNotInitialisedByUser); + } + + @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 editEventDetails.equals(e.editEventDetails); + } + + /** + * Stores the event details to be edited. Each non-empty field value will replace the + * corresponding field value of the event. + */ + public static class EditEventDetails { + private EventName eventName; + private EventDate eventDate; + private EventVenue eventVenue; + private EventStartTime eventStartTime; + private Set eventTags; + + public EditEventDetails() {} + + /** + * Copy constructor. + */ + public EditEventDetails(EditEventDetails toCopy) { + setEventName(toCopy.eventName); + setEventDate(toCopy.eventDate); + setEventVenue(toCopy.eventVenue); + setEventStartTime(toCopy.eventStartTime); + setEventTags(toCopy.eventTags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(eventName, eventDate, + eventVenue, eventStartTime, eventTags); + } + + public void setEventName(EventName eventName) { + this.eventName = eventName; + } + + public Optional getEventName() { + return Optional.ofNullable(eventName); + } + + public void setEventDate(EventDate eventDate) { + this.eventDate = eventDate; + } + + public Optional getEventDate() { + return Optional.ofNullable(eventDate); + } + + public void setEventVenue(EventVenue eventVenue) { + this.eventVenue = eventVenue; + } + + public Optional getEventVenue() { + return Optional.ofNullable(eventVenue); + } + + public void setEventStartTime(EventStartTime eventStartTime) { + this.eventStartTime = eventStartTime; + } + + public Optional getEventStartTime() { + return Optional.ofNullable(eventStartTime); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + */ + public void setEventTags(Set eventTags) { + this.eventTags = (eventTags != null) ? new HashSet<>(eventTags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getEventTags() { + return (eventTags != null) ? Optional.of(Collections.unmodifiableSet(eventTags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditEventDetails)) { + return false; + } + + // state check + EditEventDetails e = (EditEventDetails) other; + + return getEventName().equals(e.getEventName()) + && getEventDate().equals(e.getEventDate()) + && getEventVenue().equals(e.getEventVenue()) + && getEventStartTime().equals(e.getEventStartTime()) + && getEventTags().equals(e.getEventTags()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/Email.java b/src/main/java/seedu/address/logic/commands/Email.java new file mode 100644 index 000000000000..286a17c6a8c0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/Email.java @@ -0,0 +1,262 @@ +package seedu.address.logic.commands; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.FileDataSource; +import javax.imageio.ImageIO; +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import com.google.zxing.WriterException; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.util.QrUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.ui.EmailWindow; + +//@@author aaryamNUS +/** + * This abstract class is inherited by Mail, EmailAll, and ForceEmail commands, + * in order to reduce code duplicity. + */ +public abstract class Email extends Command { + private static Logger logger = Logger.getLogger("createAndSendEmailWithTicket"); + private File qrImage; + + public Email() {} + + /** + * Creates a new EmailWindow controller which subsequently launches a GUI Window to retrieve + * username, password, email message and email subject. Error handling is also performed + * through the try-catch block, which details with CommandException as well as + * General Exceptions. Once parsed, the private global variables in the MailCommand username, + * password, emailSubject, and emailMessage are set with the strings received from the EmailWindow + */ + public String[] retrieveInformation() throws CommandException { + String[] information; + EmailWindow newEmailWindow = new EmailWindow(); + + newEmailWindow.showAndWait(); + + if (newEmailWindow.isSendButton()) { + information = newEmailWindow.getInformation(); + if (!isValidEmail(information[0])) { + throw new CommandException(Messages.MESSAGE_INVALID_EMAIL); + } + } else if (newEmailWindow.isQuitButton()) { + throw new CommandException(Messages.MESSAGE_NO_EMAIL_SENT_MESSAGE); + } else { + throw new CommandException(Messages.MESSAGE_NO_EMAIL_SENT_MESSAGE); + } + + return information; + } + + /** + * Creates a connection to the host gmail account via gmail's smtp port + * @return the properties of the host domain server, in this case gmail + */ + public Properties createPropertiesConfiguration() { + // Connects to Gmail using it's smtp port and previous authorization + return new Properties() { + { + put("mail.smtp.auth", "true"); + put("mail.smtp.starttls.enable", "true"); + put("mail.smtp.host", "smtp.gmail.com"); + put("mail.smtp.port", "587"); + } + }; + } + + /** + * Creates the message of the email using the emailMessage and emailSubject parameters + * provided, and sends the email using Transport.send(). Moreover, the 'to' and 'from' + * fields are provided by the child classes. Moreover, this method is only used for the EMAIL INDEX + * command as it attaches a QR code in the email message for the event manager to scan + */ + public void createAndSendEmailWithTicket(String username, String emailSubject, String emailMessage, + String recipients, Session session, String guestUniqueId) + throws CommandException { + try { + // Creates a default MimeMessage object + Message message = new MimeMessage(session); + + // Set the email of the host + message.setFrom(new InternetAddress(username)); + + // Set the email of the guest + message.setRecipients(Message.RecipientType.BCC, + InternetAddress.parse(recipients)); + + // Set email subject and message + message.setSubject(emailSubject); + + // Generate and set the overall email message content + Multipart multipart = createQrAndEmailMessage(emailMessage, guestUniqueId); + message.setContent(multipart); + + // Send the email + Transport.send(message); + + // Delete the image file once the email has been sent + qrImage.deleteOnExit(); + } catch (MessagingException e) { + throw new CommandException(Messages.MESSAGE_NO_INTERNET_CONNECTION_OR_INVALID_CREDENTIALS); + } + } + + /** + * Generates the message of the email by generating a QR Code image and attaching it in the + * email. This command is used when the input command is email INDEX + * @param emailMessage text message written by the user + * @param guestUniqueId Unique ID of the guest to be encoded by the QR Code + * @return the multipart + */ + private Multipart createQrAndEmailMessage(String emailMessage, String guestUniqueId) { + // Create a multipart message to facilitate the attachment of images + Multipart multipart = new MimeMultipart(); + + try { + // Create the message part of the Email and set the message + BodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setText(emailMessage); + + // Set the message part + multipart.addBodyPart(messageBodyPart); + + // The second body part is the QR Code + messageBodyPart = new MimeBodyPart(); + + // Generate a QR code, which represents the guest ticket + // QR code is generated using a guest's UID, hence it is ensured to be unique + QrUtil getGuestTicket = new QrUtil(); + BufferedImage ticket = getGuestTicket.generateQr(guestUniqueId); + + // Create a temporary image file to store the BufferedImage and for it to be + // read by DataSource() + qrImage = new File("temp.png"); + ImageIO.write(ticket, "png", qrImage); + DataSource source = new FileDataSource(qrImage); + + // Set the details of the image attachment + messageBodyPart.setDataHandler(new DataHandler(source)); + messageBodyPart.setFileName("Your Ticket"); + multipart.addBodyPart(messageBodyPart); + } catch (WriterException e) { + logger.log(Level.SEVERE, "Error: exception when retrieving QRCode BufferedImage!"); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error: exception when writing the BufferedImage!"); + } catch (MessagingException e) { + logger.log(Level.SEVERE, "Error in generating QR code!"); + } + + return multipart; + } + + /** + * Creates the message of the email using the emailMessage and emailSubject parameters + * provided, and sends the email using Transport.send(). Moreover, the 'to' and 'from' + * fields are provided by the child classes. This method is used by the EmailALL and + * EmailSpecific commands + */ + public void createAndSendEmail(String username, String emailSubject, String emailMessage, + String recipients, Session session) throws CommandException { + try { + // Creates a default MimeMessage object + Message message = new MimeMessage(session); + + // Set the email of the host + message.setFrom(new InternetAddress(username)); + + // Set the email of the guest + message.setRecipients(Message.RecipientType.BCC, + InternetAddress.parse(recipients)); + + // Set email subject and message + message.setSubject(emailSubject); + message.setText(emailMessage); + + Transport.send(message); + } catch (MessagingException e) { + throw new CommandException(Messages.MESSAGE_NO_INTERNET_CONNECTION_OR_INVALID_CREDENTIALS); + } + } + + /** + * This method checks whether a given email address has the valid format, through the use + * of a Java Regular expression, which is a special sequence of characters that allows you + * to match and find other strings or sets of strings + * + * A basic outline of the 'expression' string is given below: + * + * Subexpression Meaning + * ^ Matches the beginning of the line + * $ Matches the end of the line + * [...] Matches with any character in the brackets + * \w Matches any word characters + * {2,4} Matches between 2 and 4 occurrences of preceding expressions + * + * @param guestAddress is the address of the guest you wish to send an email to + * @return a boolean that determines whether the given email address is of the correct format + * The following regular expression was adapted from zParacha.com, + * Source: http://zparacha.com/ultimate-java-regular-expression-to-validate-email-address + */ + public boolean isValidEmail (String guestAddress) { + String expression = "^[\\w\\-]([\\w])+[\\w]+@([\\w\\-]+\\.)+[A-Z]{2,4}$"; + + // Create a pattern object using the expression provided + Pattern pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE); + + // Create the corresponding matcher object + Matcher matcher = pattern.matcher(guestAddress); + return matcher.matches(); + } + + /** + * Creates the recipient string based on all of the persons to send and email to + * @param personsToSendEmail is the original list of all guests in the list + * @return the recipients string + */ + public String recipientCreator(HashSet personsToSendEmail) { + String recipients; + StringBuilder recipientBuilder = new StringBuilder(); + + // Create a string with all the recipients + for (String personToEmail : personsToSendEmail) { + String individualGuest = personToEmail + ","; + recipientBuilder.append(individualGuest); + } + + recipients = removeLastChar(recipientBuilder.toString()); + + return recipients; + } + + /** + * Removes the last character out of the recipients String as it contains + * an unwanted ',' character + * @param string is the original recipients string + * @return the substring + */ + private static String removeLastChar(String string) { + return string.substring(0, string.length() - 1); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EmailAllCommand.java b/src/main/java/seedu/address/logic/commands/EmailAllCommand.java new file mode 100644 index 000000000000..e6cf2f694a05 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EmailAllCommand.java @@ -0,0 +1,197 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +//@@author aaryamNUS +/** + * Sends an email to all of the guests in the specified list. + */ +public class EmailAllCommand extends Email { + + public static final String COMMAND_WORD = "emailAll"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sends an email to all guests in the " + + "current filtered list\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + + private static final String MESSAGE_MAIL_ALL_PERSON_SUCCESS = "Successfully sent an email to %1$d emails, " + + "could not send an email to %2$d emails will addresses: %3$s!"; + + private static Logger logger = Logger.getLogger("execute"); + private static String username; + private static String password; + private static String emailSubject; + private static String emailMessage; + + public EmailAllCommand() {} + + /** + * Sends an email to all the persons in the current filtered list + * @param model is instantiated to get the latest filtered person list + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + guestListSizeCheck(lastShownList); + + // Array of strings to store all the necessary information + String[] information; + + // Retrieve the information through a method in the abstract super class Email + // and then set the information via the 4 variables username, password, emailSubject, + // and emailMessage + information = retrieveInformation(); + setInformation(information); + + // Check for duplicate emails to ensure each guest only receives one email, even if + // multiple guests have registered under the same email + HashSet personsToSendEmail = new HashSet<>(); + + // Contains all the email addresses that aren't of a valid format + HashSet invalidEmailAddresses = new HashSet<>(); + + // These variables are used in the command success message MESSAGE_MAIL_ALL_PERSON_SUCCESS + StringBuilder invalidEmails = new StringBuilder(); + int successfulEmails = 0; + int failedEmails = 0; + + // Check for invalid or duplicate emails, and create the list of valid email addresses to send to + // NOTE: This part of the code was not abstracted out as it would involve the creation of a custom + // class object to return different parameters (StringBuilder, int, int) that would need to be instantiated + // in MailCommand and EmailSpecificCommand classes, thereby increasing dependency + // and coupling within the code + for (Person personToMail : lastShownList) { + assert personToMail != null; + + if (!isValidEmail(personToMail.getEmail().toString())) { + if (invalidEmailAddresses.contains(personToMail.getEmail().toString())) { + logger.log(Level.INFO, "Guest email address has already been marked as invalid!"); + } else { + invalidEmailAddresses.add(personToMail.getEmail().toString()); + failedEmails++; + String invalidEmail = " || " + personToMail.getEmail().toString() + " || "; + invalidEmails.append(invalidEmail); + } + } else if (isValidEmail(personToMail.getEmail().toString())) { + if (personsToSendEmail.contains(personToMail.getEmail().toString())) { + logger.log(Level.INFO, "Guest email address has already been sent an email!"); + } else { + personsToSendEmail.add(personToMail.getEmail().toString()); + successfulEmails++; + } + } + } + + // Creates a new session with the user Gmail account as the host + Properties props = createPropertiesConfiguration(); + + // Authenticate the user credentials + EmailPasswordAuthenticator authenticate = new EmailPasswordAuthenticator(); + + // Create a new session using the authenticated credentials and the properties of + // the Gmail host + Session session = Session.getDefaultInstance(props, authenticate); + + // Strings to represent the recipients of the email (i.e. all the guests in the list) + String recipients = recipientCreator(personsToSendEmail); + + createAndSendEmail(username, emailSubject, emailMessage, + recipients, session); + + logger.log(Level.INFO, "All emails sent successfully!"); + return new CommandResult(String.format(MESSAGE_MAIL_ALL_PERSON_SUCCESS, successfulEmails, + failedEmails, invalidEmails)); + } + + /** + * Makes sure that there is at least one guest in the current filtered list + * @param lastShownList the last known filtered list + */ + private void guestListSizeCheck(List lastShownList) throws CommandException { + if (lastShownList.size() == 0) { + throw new CommandException(Messages.MESSAGE_EMPTY_LIST); + } + } + + /** + * Sets the information retrieved from an EmailWindow + */ + private static void setInformation(String[] information) { + username = information[0]; + password = information[1]; + emailSubject = information[2]; + emailMessage = information[3]; + } + + /** + * Authenticates the user account based on the credentials provided + */ + private static class EmailPasswordAuthenticator extends Authenticator { + @Override + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + } + + @Override + public Properties createPropertiesConfiguration() { + return super.createPropertiesConfiguration(); + } + + @Override + public String[] retrieveInformation() throws CommandException { + return super.retrieveInformation(); + } + + @Override + public String recipientCreator(HashSet personsToSendEmail) { + return super.recipientCreator(personsToSendEmail); + } + + @Override + public void createAndSendEmail(String username, String emailSubject, String emailMessage, + String recipient, Session session) throws CommandException { + super.createAndSendEmail(username, emailSubject, emailMessage, recipient, session); + } + + @Override + public boolean isValidEmail(String guestAddress) { + return super.isValidEmail(guestAddress); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EmailAllCommand)) { + return false; + } + + // state check + EmailAllCommand e = (EmailAllCommand) other; + return e.equals(other); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EmailSpecificCommand.java b/src/main/java/seedu/address/logic/commands/EmailSpecificCommand.java new file mode 100644 index 000000000000..87c63aee75d2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EmailSpecificCommand.java @@ -0,0 +1,208 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Sends an email to any guests that have at least one of the specified tags + */ +public class EmailSpecificCommand extends Email { + + public static final String COMMAND_WORD = "emailSpecific"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sends an email to the guests that have " + + "at least one of the provided Tags.\n" + + "Parameters: " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TAG + "VIP " + PREFIX_TAG + "Paid"; + + private static final String MESSAGE_MAIL_PERSON_SUCCESS = "Successfully sent an email to %1$d emails, " + + "did not send an email to %2$d guests!"; + + private static Logger logger = Logger.getLogger("execute"); + private static String username; + private static String password; + private static String emailSubject; + private static String emailMessage; + private final Set tagsToSend; + + /** + * @param tagsToSend of the guests with the specified tags + */ + public EmailSpecificCommand(Set tagsToSend) { + requireNonNull(tagsToSend); + this.tagsToSend = tagsToSend; + } + + /** + * Sends an email to all guests that have at least one of the specified tags + * @param model must be non-null + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + guestListSizeCheck(lastShownList); + + // Check for duplicate emails to ensure each guest only receives one email, even if + // multiple guests have registered under the same email + HashSet personsToSendEmail = createMailingList(lastShownList, tagsToSend); + + // Strings to represent the recipients of the email (i.e. all the guests in the list) + String recipients = recipientCreator(personsToSendEmail); + + try { + // Array of strings to store all the necessary information + String[] information; + // Retrieve the information through a method in the super class Email + information = retrieveInformation(); + setInformation(information); + + // Creates a new session with the user Gmail account as the host + Properties props = createPropertiesConfiguration(); + + // Authenticate the user credentials + EmailPasswordAuthenticator authenticate = new EmailPasswordAuthenticator(); + + // Create a new session using the authenticated credentials and the properties of + // the Gmail host + Session session = Session.getDefaultInstance(props, authenticate); + + createAndSendEmail(username, emailSubject, emailMessage, + recipients, session); + } catch (NullPointerException ne) { + logger.log(Level.SEVERE, "Error: retrieving information was unsuccessful!"); + } + + logger.log(Level.INFO, "Emails sent successfully to all guests with the specified tags!"); + return new CommandResult(String.format(MESSAGE_MAIL_PERSON_SUCCESS, personsToSendEmail.size(), + lastShownList.size() - personsToSendEmail.size())); + } + + /** + * Makes sure that there is at least one guest in the current filtered list + * @param lastShownList the last known filtered list + */ + private void guestListSizeCheck(List lastShownList) throws CommandException { + if (lastShownList.size() == 0) { + throw new CommandException(Messages.MESSAGE_EMPTY_LIST); + } + } + + /** + * Sets the information retrieved from an EmailWindow + */ + private static void setInformation(String[] information) { + username = information[0]; + password = information[1]; + emailSubject = information[2]; + emailMessage = information[3]; + } + + /** + * Create a mailing list with all the email addresses of guests, this method + * also filters out duplicate or invalid email addresses + * @param lastShownList the last known guest list + * @param tagsToSend list of tags to match with guests + * @return the email list + */ + private HashSet createMailingList(List lastShownList, Set tagsToSend) throws CommandException { + HashSet personsToSendEmail = new HashSet<>(); + + // First check which of the guests have at least one of the specified tags, + // and filter out any duplicate guests + for (Person individualGuests : lastShownList) { + for (Tag singleTags : tagsToSend) { + if (individualGuests.getTags().contains(singleTags)) { + if (personsToSendEmail.contains(individualGuests.getEmail().toString())) { + logger.log(Level.INFO, "Guest email address has already been sent an email!"); + } else if (!isValidEmail(individualGuests.getEmail().toString())) { + logger.log(Level.INFO, "Guest email address is not valid!"); + } else { + personsToSendEmail.add(individualGuests.getEmail().toString()); + } + } + } + } + + // If no guests have the specified tags, throw a Command Exception + if (personsToSendEmail.isEmpty()) { + throw new CommandException(Messages.MESSAGE_NO_PERSONS_WITH_TAGS); + } + + return personsToSendEmail; + } + + @Override + public Properties createPropertiesConfiguration() { + return super.createPropertiesConfiguration(); + } + + @Override + public String[] retrieveInformation() throws CommandException { + return super.retrieveInformation(); + } + + @Override + public String recipientCreator(HashSet personsToSendEmail) { + return super.recipientCreator(personsToSendEmail); + } + + /** + * Authenticates the user account based on the credentials provided + */ + private static class EmailPasswordAuthenticator extends Authenticator { + @Override + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + } + + @Override + public void createAndSendEmail(String username, String emailSubject, String emailMessage, + String recipient, Session session) throws CommandException { + super.createAndSendEmail(username, emailSubject, emailMessage, recipient, session); + } + + @Override + public boolean isValidEmail(String guestAddress) { + return super.isValidEmail(guestAddress); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EmailSpecificCommand)) { + return false; + } + + // state check + EmailSpecificCommand e = (EmailSpecificCommand) other; + return tagsToSend.equals(e.tagsToSend); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index e848fa918964..1cfed6a4ef54 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -14,6 +14,11 @@ public class ExitCommand extends Command { public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exits the application.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model, CommandHistory history) { EventsCenter.getInstance().post(new ExitAppRequestEvent()); diff --git a/src/main/java/seedu/address/logic/commands/ExportCommand.java b/src/main/java/seedu/address/logic/commands/ExportCommand.java new file mode 100644 index 000000000000..157f89663590 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ExportCommand.java @@ -0,0 +1,116 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.NoSuchFileException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.converters.PersonConverter; +import seedu.address.logic.converters.exceptions.PersonEncodingException; +import seedu.address.logic.converters.fileformats.AdaptedPerson; +import seedu.address.logic.converters.fileformats.SupportedFile; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +//@@author wm28 +/** + * Exports currently filtered guest list to a CSV file + */ +public class ExportCommand extends Command { + + public static final String COMMAND_WORD = "export"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exports guests to a CSV file. " + + "Parameters: FILE_PATH\n" + + "Example: " + COMMAND_WORD + " guestbook.csv"; + public static final String MESSAGE_EXPORT_CSV_RESULT = "Successfully exported %1$d/%2$d guests to %3$s"; + public static final String MESSAGE_NO_PERSONS = "There are no persons to export!"; + + private static Logger logger = Logger.getLogger("execute"); + + private final SupportedFile supportedFile; + private final PersonConverter personConverter; + private int totalPersons; + private int successfulExports; + + public ExportCommand(SupportedFile supportedFile, PersonConverter personConverter) { + assert supportedFile != null : "supportedFile cannot be null"; + assert personConverter != null : "personConverter cannot be null"; + assert personConverter.getSupportedFileFormat().equals(supportedFile.getSupportedFileFormat()) + : "supportedFile and personConverter does not support the same file format"; + this.personConverter = personConverter; + this.supportedFile = supportedFile; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + ObservableList filteredList = model.getFilteredPersonList(); + if (filteredList.size() == 0) { + logger.log(Level.INFO, "No persons to export from the guest list"); + throw new CommandException(MESSAGE_NO_PERSONS); + } + totalPersons = filteredList.size(); + successfulExports = totalPersons; + + try { + List result = exportPersons(filteredList); + supportedFile.writeAdaptedPersons(result); + } catch (NoSuchFileException nsfe) { + logger.log(Level.INFO, "File path provided is invalid"); + String errorMessage = String.format(Messages.MESSAGE_INVALID_FILE_PATH, nsfe.getMessage()); + throw new CommandException(errorMessage, nsfe); + } catch (FileAlreadyExistsException faee) { + logger.log(Level.INFO, "CSV File provided already exist", supportedFile.getFileName()); + throw new CommandException(faee.getMessage(), faee); + } catch (IOException ioe) { + logger.log(Level.INFO, "Failed to read from SupportedFile"); + throw new CommandException(ioe.getMessage(), ioe); + } + + return new CommandResult(String.format(MESSAGE_EXPORT_CSV_RESULT, + successfulExports, totalPersons, supportedFile.getFileName())); + + } + + /** + * Exports persons to csv-formatted strings + */ + private List exportPersons(ObservableList personList) { + List result = new ArrayList<>(); + for (Person person : personList) { + try { + result.add(personConverter.encodePerson(person)); + } catch (PersonEncodingException pee) { + successfulExports--; + } + } + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ExportCommand)) { + return false; + } + + ExportCommand otherEc = (ExportCommand) other; + return supportedFile.getFileName().equals(otherEc.supportedFile.getFileName()) + && personConverter.getSupportedFileFormat().equals(otherEc.personConverter.getSupportedFileFormat()); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java new file mode 100644 index 000000000000..d990eda71303 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -0,0 +1,45 @@ +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.person.ContainsKeywordsPredicate; + +//@@author Sarah +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FilterCommand extends Command { + public static final String COMMAND_WORD = "filter"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose tags contain all of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: pa/KEYWORD a/MORE_KEYWORDS t/MORE_KEYWORDS...\n" + + "Example: " + COMMAND_WORD + "pa/PAYMENT_STATUS a/ATTENDANCE_STATUS " + + "t/TAG..."; + + private final ContainsKeywordsPredicate predicate; + + public FilterCommand(ContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult(String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterCommand // instanceof handles nulls + && predicate.equals(((FilterCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index beb178e3a3f5..b700cc72c209 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. + * Finds and lists all persons in address book whose name, phone number or email + * contains any of the argument keywords. * Keyword 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 " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names, phone number" + + " or email contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Parameters: n/KEYWORD p/MORE_KEYWORDS e/MORE_KEYWORDS...\n" + + "Example: " + COMMAND_WORD + " n/alice n/bob n/charlie p/82736479 e/alice@u.nus.edu"; private final NameContainsKeywordsPredicate predicate; diff --git a/src/main/java/seedu/address/logic/commands/GeneralMarkCommand.java b/src/main/java/seedu/address/logic/commands/GeneralMarkCommand.java new file mode 100644 index 000000000000..9a2a63b54724 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/GeneralMarkCommand.java @@ -0,0 +1,265 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Attendance; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; +import seedu.address.model.tag.Tag; + +//@@author kronicler + +/** + * Edits the details of an existing person in the address book. + */ +public abstract class GeneralMarkCommand extends Command { + public static final String MESSAGE_MARK_PERSON_SUCCESS = "Marked Person as PRESENT: %1$s"; + public static final String MESSAGE_UNMARK_PERSON_SUCCESS = "Marked Person as ABSENT: %1$s"; + public static final String MESSAGE_UID_NOT_FOUND = "UID not found in the guest list.\n"; + public static final String MESSAGE_UID_DUPLICATE = "WARNING: There is more than one person with the same UID" + + " in the guest list."; + + private final Uid uid; + private Index index; + private EditPersonDescriptor editPersonDescriptor; + + /** + * @param uid of the person in the filtered person list to edit + */ + public GeneralMarkCommand(Uid uid) { + requireNonNull(uid); + this.uid = uid; + } + + /** + * Scans through the list and compares the phone numbers to the one that is being searched + * Assigns the index of the found person to the index of the command. + * @param lastShownList {@code CommandHistory} which the command should operate on. + * @throws CommandException if there are no matching persons in the list + */ + public void retrieveIndex(List lastShownList) throws CommandException { + int iterator = 0; + int found = 0; + int location = 0; + for (Person p : lastShownList) { + Uid temp = p.getUid(); + if (uid.equals(temp)) { + found++; + location = iterator; + } + iterator++; + } + if (found > 1) { + throw new CommandException(MESSAGE_UID_DUPLICATE); + } + if (found == 0) { + throw new CommandException(MESSAGE_UID_NOT_FOUND); + } + + index = Index.fromZeroBased(location); + } + + /** + * Performs the task of attendance taking in the guest list. + * This method handles both marking the person as Present or Absent. + * @param model which the command should operate on to find the person's information + * @throws CommandException when there is no such person with the identifier in the list + */ + public CommandResult performAttendanceTaking(Model model, boolean isMark) + throws CommandException { + requireNonNull(model); + //List lastShownList = model.getFilteredPersonList(); + List guestList = model.getAddressBook().getPersonList(); + + retrieveIndex(guestList); + + Person personToEdit = guestList.get(index.getZeroBased()); + + if (isMark) { + editPersonDescriptor = new EditPersonDescriptor("PRESENT"); + } else { + editPersonDescriptor = new EditPersonDescriptor("ABSENT"); + } + + Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + model.updatePerson(personToEdit, editedPerson); + model.commitAddressBook(); + if (isMark) { + return new CommandResult(String.format(MESSAGE_MARK_PERSON_SUCCESS, editedPerson)); + } + return new CommandResult(String.format(MESSAGE_UNMARK_PERSON_SUCCESS, editedPerson)); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + public Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + assert personToEdit != null; + + Name updatedName = personToEdit.getName(); + Phone updatedPhone = personToEdit.getPhone(); + Email updatedEmail = personToEdit.getEmail(); + //@@author + //@@author Sarah + Payment updatedPayment = personToEdit.getPayment(); + //@@author + //@@author kronicler + Attendance updatedAttendance = editPersonDescriptor.getAttendance().orElse(personToEdit.getAttendance()); + Uid updatedUid = editPersonDescriptor.getUid().orElse(personToEdit.getUid()); + Set updatedTags = personToEdit.getTags(); + + return new Person(updatedName, updatedPhone, updatedEmail, updatedPayment, + updatedAttendance, updatedUid, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instance of handles nulls + if (!(other instanceof GeneralMarkCommand)) { + return false; + } + + // state check + GeneralMarkCommand e = (GeneralMarkCommand) other; + return index.equals(e.index) + && editPersonDescriptor.equals(e.editPersonDescriptor); + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditPersonDescriptor { + private Name name; + private Phone phone; + private Email email; + private Payment payment; + private Attendance attendance; + private Uid uid; + private Set tags; + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPersonDescriptor(String updateAttendance) { + setName(null); + setPhone(null); + setEmail(null); + setPayment(null); + setAttendance(new Attendance(updateAttendance)); + setUid(null); + setTags(null); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + //@@author + //@@author Sarah + public void setPayment(Payment payment) { + this.payment = payment; + } + + public Optional getPayment() { + return Optional.ofNullable(payment); + } + //@@author + //@@author kronicler + public void setAttendance(Attendance attendance) { + this.attendance = attendance; + } + + public Optional getAttendance() { + return Optional.ofNullable(attendance); + } + + public void setUid(Uid uid) { + this.uid = uid; + } + + public Optional getUid() { + return Optional.ofNullable(uid); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getPayment().equals(e.getPayment()) + && getAttendance().equals(e.getAttendance()) + && getUid().equals(e.getUid()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 66305e95d8f2..4b675f3d2064 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -13,6 +13,8 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f1541fb57f20..102832562ce6 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -17,6 +17,11 @@ public class HistoryCommand extends Command { public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists a history of commands.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(history); diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java new file mode 100644 index 000000000000..b85bae48ea2c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -0,0 +1,165 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ShowImportReportEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.converters.PersonConverter; +import seedu.address.logic.converters.error.ImportError; +import seedu.address.logic.converters.exceptions.PersonDecodingException; +import seedu.address.logic.converters.fileformats.AdaptedPerson; +import seedu.address.logic.converters.fileformats.SupportedFile; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.person.Uid; + +//@@author wm28 + +/** + * Imports multiple guests into the guest list of the current event via a CSV file + */ +public class ImportCommand extends Command { + + public static final String COMMAND_WORD = "import"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports guests into current event through a CSV file. " + + "Parameters: FILE_PATH\n" + + "Example: " + COMMAND_WORD + " guestbook.csv"; + + public static final String MESSAGE_IMPORT_CSV_RESULT = "Successfully imported %1$d of %2$d guests from %3$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_DUPLICATE_UID = + "This UID is already used in the guest list. Please use another UID."; + public static final Uid DEFAULT_TO_GENERATE_UID = new Uid("00000"); + + + private static Logger logger = Logger.getLogger("execute"); + + private final SupportedFile supportedFile; + private final PersonConverter personConverter; + private List errors; + private int successfulImports; + private int totalImports; + + + public ImportCommand(SupportedFile supportedFile, PersonConverter personConverter) { + assert supportedFile != null : "SupportedFile cannot be null"; + assert personConverter != null : "personConverter cannot be null"; + assert personConverter.getSupportedFileFormat().equals(supportedFile.getSupportedFileFormat()) + : "supportedFile and personConverter does not support the same file format"; + this.supportedFile = supportedFile; + this.personConverter = personConverter; + errors = new ArrayList<>(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + try { + List persons = supportedFile.readAdaptedPersons(); + successfulImports = persons.size(); + totalImports = successfulImports; + importPersons(persons, model); + } catch (IOException ioe) { + logger.log(Level.INFO, "Failed to read from CSV file: " + supportedFile.getFileName()); + throw new CommandException(ioe.getMessage(), ioe); + } + + if (!errors.isEmpty()) { + logger.log(Level.INFO, "Error exists in CSV file, triggering ImportReportWindow"); + EventsCenter.getInstance().post(new ShowImportReportEvent(errors)); + } + + if (successfulImports > 0) { + model.commitAddressBook(); + } + return new CommandResult(String.format(MESSAGE_IMPORT_CSV_RESULT, + successfulImports, totalImports, supportedFile.getFileName())); + } + + /** + * Imports persons to the guest list + */ + private void importPersons(List persons, Model model) { + for (AdaptedPerson person : persons) { + try { + Person toAdd = personConverter.decodePerson(person); + addPerson(toAdd, model); + } catch (PersonDecodingException pe) { + errors.add(new ImportError(person.getFormattedString(), pe.getMessage())); + successfulImports--; + } catch (CommandException ce) { + errors.add(new ImportError(person.getFormattedString(), ce.getMessage())); + successfulImports--; + } + } + } + + /** + * Adds a person to the guest list + */ + private void addPerson(Person toAdd, Model model) throws CommandException { + if (model.hasPerson(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + //@@author + //@@author kronicler + if (model.hasUid(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_UID); + } + + if (toAdd.getUid().equals(DEFAULT_TO_GENERATE_UID)) { + boolean unique = false; + while (!unique) { + Person temp = new Person(toAdd.getName(), toAdd.getPhone(), toAdd.getEmail(), toAdd.getPayment(), + toAdd.getAttendance(), generateUid(), toAdd.getTags()); + if (model.hasUid(temp) == false) { + unique = true; + toAdd = temp; + } + } + } + //@@author + //@@wm28 + model.addPerson(toAdd); + } + + //@@author + //@@author kronicler + /** + * Generate a random UID + */ + public Uid generateUid() { + Random rnd = new Random(); + int number = rnd.nextInt(999999); + return new Uid(String.format("%06d", number)); + } + //@@author + //@@author wm28 + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ImportCommand)) { + return false; + } + + ImportCommand otherIc = (ImportCommand) other; + return supportedFile.getFileName().equals(otherIc.supportedFile.getFileName()) + && personConverter.getSupportedFileFormat().equals(otherIc.personConverter.getSupportedFileFormat()); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..2d369d93131b 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -15,6 +15,10 @@ public class ListCommand extends Command { public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all the guests.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/address/logic/commands/MailCommand.java b/src/main/java/seedu/address/logic/commands/MailCommand.java new file mode 100644 index 000000000000..b7667c21f5ca --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MailCommand.java @@ -0,0 +1,170 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +//@@author aaryamNUS +/** + * Sends an email to the specified person in the guest list. + */ +public class MailCommand extends Email { + + public static final String COMMAND_WORD = "email"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sends an email to the specified person " + + "provided by INDEX.\n" + + "Parameters: INDEX (must be a positive integer) " + + "Example: " + COMMAND_WORD + " 1 "; + + private static final String MESSAGE_MAIL_PERSON_SUCCESS = "Successfully sent email!"; + + private static Logger logger = Logger.getLogger("execute"); + private static String username; + private static String password; + private static String emailSubject; + private static String emailMessage; + private Index index; + + /** + * @param index of the person in the filtered person list to edit + */ + public MailCommand(Index index) { + requireNonNull(index); + + this.index = index; + } + + /** + * Sends an email to the person at the specified INDEX + * @param model + * Note: the following code was adapted from the SendEmail.java class code provided by @Rish on stackoverflow + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + try { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + Person personToMail = lastShownList.get(index.getZeroBased()); + errorChecking(personToMail, lastShownList); + + // Array of strings to store all the necessary information + String[] information; + // Retrieve the information through a method in the super class Email + information = retrieveInformation(); + setInformation(information); + + // Creates a new session with the user gmail account as the host + Properties props = createPropertiesConfiguration(); + + // Authenticate the user credentials + EmailPasswordAuthenticator authenticate = new EmailPasswordAuthenticator(); + + // Create a new session using the authenticated credentials and the properties of + // the Gmail host + Session session = Session.getDefaultInstance(props, authenticate); + + // Create a QR code which resembles the ticket of the guest, using their unique ID field + assert (personToMail.getUid() == null) : "Every guest must have a unique ID"; + + createAndSendEmailWithTicket(username, emailSubject, emailMessage, + personToMail.getEmail().toString(), session, "mark " + personToMail.getUid().toString()); + } catch (NullPointerException ne) { + logger.log(Level.SEVERE, "Error: retrieving information was unsuccessful!"); + } catch (IndexOutOfBoundsException ie) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + logger.log(Level.INFO, "Email sent successfully"); + return new CommandResult(MESSAGE_MAIL_PERSON_SUCCESS); + } + + /** + * Sets the information retrieved from an EmailWindow + */ + private static void setInformation(String[] information) { + username = information[0]; + password = information[1]; + emailSubject = information[2]; + emailMessage = information[3]; + } + + /** + * Makes sure that INDEX is not null and that the email address of the + * guest to mail is valid + * @param personToMail the guest to be sent an email + */ + private void errorChecking(Person personToMail, List lastShownList) throws CommandException { + assert index != null; + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + assert personToMail != null; + if (!isValidEmail(personToMail.getEmail().toString())) { + throw new CommandException("Error: The email of the recipient is invalid!"); + } + } + + @Override + public Properties createPropertiesConfiguration() { + return super.createPropertiesConfiguration(); + } + + @Override + public String[] retrieveInformation() throws CommandException { + return super.retrieveInformation(); + } + + /** + * Authenticates the user account based on the credentials provided + */ + private static class EmailPasswordAuthenticator extends Authenticator { + @Override + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + } + + @Override + public void createAndSendEmailWithTicket(String username, String emailSubject, String emailMessage, + String recipient, Session session, String guestUniqueId) throws CommandException { + super.createAndSendEmailWithTicket(username, emailSubject, emailMessage, recipient, session, guestUniqueId); + } + + @Override + public boolean isValidEmail(String guestAddress) { + return super.isValidEmail(guestAddress); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MailCommand)) { + return false; + } + + // state check + MailCommand e = (MailCommand) other; + return index.equals(e.index); + } +} diff --git a/src/main/java/seedu/address/logic/commands/MarkCommand.java b/src/main/java/seedu/address/logic/commands/MarkCommand.java new file mode 100644 index 000000000000..0a65d2db206d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MarkCommand.java @@ -0,0 +1,190 @@ +package seedu.address.logic.commands; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Attendance; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; +import seedu.address.model.tag.Tag; + +//@@author kronicler +/** + * Edits the details of an existing person in the address book. + */ +public class MarkCommand extends GeneralMarkCommand { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a person as present " + + "using their unique UID. " + + "This will also change the attendance associated with the person to Present.\n" + + "Parameters: " + + "[UID]\n" + + "Example: " + COMMAND_WORD + + " 708944"; + + private final Uid uid; + private Index index; + private final EditPersonDescriptor editPersonDescriptor; + + /** + * @param uid of the person in the filtered person list to edit + */ + public MarkCommand(Uid uid) { + super(uid); + this.uid = uid; + this.editPersonDescriptor = new EditPersonDescriptor(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + return super.performAttendanceTaking(model, true); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MarkCommand)) { + return false; + } + + // state check + MarkCommand e = (MarkCommand) other; + return index.equals(e.index) + && editPersonDescriptor.equals(e.editPersonDescriptor); + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditPersonDescriptor { + private Name name; + private Phone phone; + private Email email; + private Payment payment; + private Attendance attendance; + private Uid uid; + private Set tags; + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPersonDescriptor() { + setName(null); + setPhone(null); + setEmail(null); + setPayment(null); + setAttendance(new Attendance("PRESENT")); + setUid(null); + setTags(null); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + //@@author + //@@author Sarah + public void setPayment(Payment payment) { + this.payment = payment; + } + + public Optional getPayment() { + return Optional.ofNullable(payment); + } + //@@author + //@@author kronicler + public void setAttendance(Attendance attendance) { + this.attendance = attendance; + } + + public Optional getAttendance() { + return Optional.ofNullable(attendance); + } + + public void setUid(Uid uid) { + this.uid = uid; + } + + public Optional getUid() { + return Optional.ofNullable(uid); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getPayment().equals(e.getPayment()) + && getAttendance().equals(e.getAttendance()) + && getUid().equals(e.getUid()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..8df27eb18fb5 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -16,6 +16,11 @@ public class RedoCommand extends Command { public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Redo the last command.\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); diff --git a/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java new file mode 100644 index 000000000000..bb9d0ac9da90 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java @@ -0,0 +1,121 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Removes a set of tags from all the people in the current GuestBook + */ +public class RemoveTagCommand extends Command { + public static final String COMMAND_WORD = "removeTag"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the specified tag " + + "from all persons in the list.\n" + + "Parameters: " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TAG + "VIP " + PREFIX_TAG + "Paid"; + + public static final String MESSAGE_REMOVED_TAG_SUCCESS = "Successfully removed all tags from %1$d persons"; + public static final String MESSAGE_NO_PERSON_WITH_TAG = "No persons in the list have the specified tags"; + + private static Logger logger = Logger.getLogger("calculateNumberOfPeopleToChange"); + private int numberOfPeopleToChange = 0; + private final Set tagsToRemove; + + /** + * @param tagsToRemove of the person in the filtered person list to edit + */ + public RemoveTagCommand(Set tagsToRemove) { + requireNonNull(tagsToRemove); + this.tagsToRemove = tagsToRemove; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List currentList = model.getFilteredPersonList(); + ReadOnlyAddressBook currentAddressBookReadOnly = model.getAddressBook(); + + // Uses edited AddressBook Model to make an editable AddressBook for removeTag() to work + AddressBook currentAddressBook = new AddressBook(currentAddressBookReadOnly); + + // Calculates number of guests to change, and performs the removeTag() command afterwards + calculateNumberOfPeopleToChange(currentList); + removeTags(model, currentAddressBook); + + return new CommandResult(String.format(MESSAGE_REMOVED_TAG_SUCCESS, numberOfPeopleToChange)); + } + + /** + * Calculates how many people in the list have at least one tag matching with the set of + * tags to be removed. + * @param currentList the current list of guests + */ + private void calculateNumberOfPeopleToChange(List currentList) { + assert numberOfPeopleToChange == 0 : "numberOfPeopleToChange should start at 0"; + + Set currentTags; + + for (Person personToBeEdited : currentList) { + currentTags = personToBeEdited.getTags(); + for (Tag tagToBeRemoved: tagsToRemove) { + try { + if (currentTags.contains(tagToBeRemoved)) { + numberOfPeopleToChange++; + break; + } + } catch (IllegalArgumentException ex) { + logger.log(Level.WARNING, "Incorrect format for tags", ex); + } + } + } + } + + /** + * Performs the remove tag function by removing a set of tags from all guests in the + * guest list and then updated the model addressBook accordingly + */ + private void removeTags(Model model, AddressBook currentAddressBook) throws CommandException { + if (numberOfPeopleToChange == 0) { + throw new CommandException(MESSAGE_NO_PERSON_WITH_TAG); + } else { + for (Tag tagToBeRemoved: tagsToRemove) { + currentAddressBook.removeTag(tagToBeRemoved); + } + logger.log(Level.INFO, "All tags removed successfully"); + + model.resetData(currentAddressBook); + model.commitAddressBook(); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof RemoveTagCommand)) { + return false; + } + // state check + RemoveTagCommand e = (RemoveTagCommand) other; + return tagsToRemove.equals(e.tagsToRemove); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..e720aa2d7fa0 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -16,6 +16,11 @@ public class UndoCommand extends Command { public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Undo the last command\n" + + "Parameters: none\n" + + "Please ensure you don't enter any characters after the command word!\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); diff --git a/src/main/java/seedu/address/logic/commands/UnmarkCommand.java b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java new file mode 100644 index 000000000000..19e7101548b1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java @@ -0,0 +1,190 @@ +package seedu.address.logic.commands; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Attendance; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; +import seedu.address.model.tag.Tag; + +//@@author kronicler +/** + * Edits the details of an existing person in the address book. + */ +public class UnmarkCommand extends GeneralMarkCommand { + + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a person as absent " + + "using their unique UID. " + + "This will also change the attendance associated with the person to Absent.\n" + + "Parameters: " + + "[UID]\n" + + "Example: " + COMMAND_WORD + + " 708944 "; + + public static final String MESSAGE_MARK_PERSON_SUCCESS = "Marked person as ABSENT: %1$s"; + public static final String MESSAGE_NOT_EDITED = "UID not found in the address book"; + + private final Uid uid; + private Index index; + private final EditPersonDescriptor editPersonDescriptor; + + /** + * @param uid of the person in the filtered person list to edit + */ + public UnmarkCommand(Uid uid) { + super(uid); + this.uid = uid; + this.editPersonDescriptor = new EditPersonDescriptor(); + } + + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + return super.performAttendanceTaking(model, false); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnmarkCommand)) { + return false; + } + + // state check + UnmarkCommand e = (UnmarkCommand) other; + return index.equals(e.index) + && editPersonDescriptor.equals(e.editPersonDescriptor); + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditPersonDescriptor { + private Name name; + private Phone phone; + private Email email; + private Payment payment; + private Attendance attendance; + private Uid uid; + private Set tags; + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPersonDescriptor() { + setName(null); + setPhone(null); + setEmail(null); + setPayment(null); + setAttendance(new Attendance("ABSENT")); + setUid(null); + setTags(null); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setPayment(Payment payment) { + this.payment = payment; + } + + public Optional getPayment() { + return Optional.ofNullable(payment); + } + + public void setAttendance(Attendance attendance) { + this.attendance = attendance; + } + + public Optional getAttendance() { + return Optional.ofNullable(attendance); + } + + public void setUid(Uid uid) { + this.uid = uid; + } + + public Optional getUid() { + return Optional.ofNullable(uid); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getPayment().equals(e.getPayment()) + && getAttendance().equals(e.getAttendance()) + && getUid().equals(e.getUid()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/seedu/address/logic/converters/CsvPersonConverter.java b/src/main/java/seedu/address/logic/converters/CsvPersonConverter.java new file mode 100644 index 000000000000..5d2e4e29b4e4 --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/CsvPersonConverter.java @@ -0,0 +1,121 @@ +package seedu.address.logic.converters; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.converters.exceptions.PersonDecodingException; +import seedu.address.logic.converters.exceptions.PersonEncodingException; +import seedu.address.logic.converters.fileformats.AdaptedPerson; +import seedu.address.logic.converters.fileformats.SupportedFileFormat; +import seedu.address.logic.converters.fileformats.csv.CsvAdaptedPerson; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Attendance; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; +import seedu.address.model.tag.Tag; + +//@@author wm28 + +/** + * Converts a person between the {@code CsvAdaptedPerson} and the {@code Person} + */ +public class CsvPersonConverter implements PersonConverter { + private static final Pattern PERSON_CSV_INPUT_FORMAT = Pattern.compile("[\"|']?(?[^\"',]*)[\"|']?," + + "[\"|']?(?[^\"',]*)[\"|']?," + + "[\"|']?(?[^\"',]*)[\"|']?," + + "[\"|']?(?[^\"',]*)[\"|']?," + + "[\"|']?(?[^\"',]*)[\"|']?," + + "[\"|']?(?[^\"',]*)[\"|']?,?" + + "(?.*)"); + + private final SupportedFileFormat supportedFileFormat = SupportedFileFormat.CSV; + + /** + * Encodes a {@code Person} object to a csv-formatted person, {@code CsvAdaptedPerson}. + * + * @param person to be encoded + * @return AdaptedPerson which which is an instance of the CsvAdaptedPerson class. + * @throws PersonEncodingException if the person fails to encode + */ + @Override + public AdaptedPerson encodePerson(Person person) throws PersonEncodingException { + if (person == null) { + throw new PersonEncodingException("Person is null"); + } + StringBuilder result = new StringBuilder(); + result.append(person.getName() + ","); + result.append(person.getPhone() + ","); + result.append(person.getEmail() + ","); + result.append(person.getPayment() + ","); + result.append(person.getAttendance() + ","); + result.append(person.getUid()); + if (!person.getTags().isEmpty()) { + result.append(","); + result.append(person.getTags().stream() + .map(tag -> tag.tagName) + .collect(Collectors.joining(","))); + } + return new CsvAdaptedPerson(result.toString()); + } + + /** + * Decodes csv-formatted person,{@code CsvAdaptedPerson}, into a {@code Person} object. + * + * @param personInput Csv-formatted person input string + * @return Person based on the csv-formatted input string of the guest + * @throws PersonDecodingException if the csv input does not conform to the expected format + */ + @Override + public Person decodePerson(AdaptedPerson personInput) throws PersonDecodingException { + Matcher matcher = PERSON_CSV_INPUT_FORMAT.matcher(personInput.getFormattedString().trim()); + Person person; + if (!matcher.matches()) { + throw new PersonDecodingException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + try { + Name name = ParserUtil.parseName(matcher.group("name")); + Phone phone = ParserUtil.parsePhone(matcher.group("phone")); + Email email = ParserUtil.parseEmail(matcher.group("email")); + Payment payment = ParserUtil.parsePayment(matcher.group("payment")); + Attendance attendance = ParserUtil.parseAttendance(matcher.group("attendance")); + Uid uid = ParserUtil.parseUid(matcher.group("uid")); + Set tagList = splitTags(matcher.group("tags")); + person = new Person(name, phone, email, payment, attendance, uid, tagList); + } catch (ParseException pe) { + throw new PersonDecodingException(pe.getMessage(), pe); + } + return person; + } + + @Override + public SupportedFileFormat getSupportedFileFormat() { + return supportedFileFormat; + } + + /** + * Splits and parses the tags into a set of Tags + * + * @param tags String input tags + * @return set of parsed Tags + * @throws ParseException if the csv input does not conform to the expected format + */ + private Set splitTags(String tags) throws ParseException { + if (tags.trim().isEmpty()) { + return new HashSet<>(); + } + return ParserUtil.parseTags(Arrays.asList(tags.split(","))); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/converters/PersonConverter.java b/src/main/java/seedu/address/logic/converters/PersonConverter.java new file mode 100644 index 000000000000..4213249602dd --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/PersonConverter.java @@ -0,0 +1,33 @@ +package seedu.address.logic.converters; + +import seedu.address.logic.converters.exceptions.PersonDecodingException; +import seedu.address.logic.converters.exceptions.PersonEncodingException; +import seedu.address.logic.converters.fileformats.AdaptedPerson; +import seedu.address.logic.converters.fileformats.SupportedFileFormat; +import seedu.address.model.person.Person; + +//@@author wm28 +/** + * Represents a Converter that is able to convert between a {@code Person} and an {@code AdaptedPerson}. + */ +public interface PersonConverter { + + /** + * Encodes {@code Person} into an {@code AdaptedPerson}. + * @throws PersonEncodingException if {@code person} does not conform the expected format + */ + AdaptedPerson encodePerson(Person person) throws PersonEncodingException; + + /** + * Decodes {@code AdaptedPerson} into a {@code Person}. + * @throws PersonDecodingException if {@code person} does not conform the expected format + */ + Person decodePerson(AdaptedPerson person) throws PersonDecodingException; + + /** + * Returns the file format the particular PersonConverter supports + */ + SupportedFileFormat getSupportedFileFormat(); +} +//@@author + diff --git a/src/main/java/seedu/address/logic/converters/error/ImportError.java b/src/main/java/seedu/address/logic/converters/error/ImportError.java new file mode 100644 index 000000000000..d4396067a3b3 --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/error/ImportError.java @@ -0,0 +1,33 @@ +package seedu.address.logic.converters.error; + +import javafx.beans.property.SimpleStringProperty; + +/** + * Represents an import error + */ +public class ImportError { + private final SimpleStringProperty dataInput; + private final SimpleStringProperty errorMessage; + + public ImportError(String dataInput, String errorMessage) { + this.dataInput = new SimpleStringProperty(dataInput); + this.errorMessage = new SimpleStringProperty(errorMessage); + } + + public String getDataInput() { + return dataInput.get(); + } + + public SimpleStringProperty dataInputProperty() { + return dataInput; + } + + public String getErrorMessage() { + return errorMessage.get(); + } + + public SimpleStringProperty errorMessageProperty() { + return errorMessage; + } + +} diff --git a/src/main/java/seedu/address/logic/converters/exceptions/PersonDecodingException.java b/src/main/java/seedu/address/logic/converters/exceptions/PersonDecodingException.java new file mode 100644 index 000000000000..1b89ee42b483 --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/exceptions/PersonDecodingException.java @@ -0,0 +1,18 @@ +package seedu.address.logic.converters.exceptions; + +//@@author wm28 + +/** + * Represents a decoding error encountered by a PersonConverter. + */ +public class PersonDecodingException extends Exception { + public PersonDecodingException(String message) { + super(message); + } + + public PersonDecodingException(String message, Throwable cause) { + super(message, cause); + } +} +//@@author + diff --git a/src/main/java/seedu/address/logic/converters/exceptions/PersonEncodingException.java b/src/main/java/seedu/address/logic/converters/exceptions/PersonEncodingException.java new file mode 100644 index 000000000000..f89efffeaad4 --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/exceptions/PersonEncodingException.java @@ -0,0 +1,18 @@ +package seedu.address.logic.converters.exceptions; + +//@@author wm28 + +/** + * Represents an encoding error encountered by a PersonConverter. + */ +public class PersonEncodingException extends Exception { + public PersonEncodingException(String message) { + super(message); + } + + public PersonEncodingException(String message, Throwable cause) { + super(message, cause); + } +} +//@@author + diff --git a/src/main/java/seedu/address/logic/converters/fileformats/AdaptedPerson.java b/src/main/java/seedu/address/logic/converters/fileformats/AdaptedPerson.java new file mode 100644 index 000000000000..7c88e15c9b4d --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/fileformats/AdaptedPerson.java @@ -0,0 +1,10 @@ +package seedu.address.logic.converters.fileformats; + +//@@author wm28 +/** + * Represents a Person formatted according to any the supported file formats + */ +public abstract class AdaptedPerson { + public abstract String getFormattedString(); +} +//@@author diff --git a/src/main/java/seedu/address/logic/converters/fileformats/SupportedFile.java b/src/main/java/seedu/address/logic/converters/fileformats/SupportedFile.java new file mode 100644 index 000000000000..3aa01d96e718 --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/fileformats/SupportedFile.java @@ -0,0 +1,32 @@ +package seedu.address.logic.converters.fileformats; + +import java.io.IOException; +import java.util.List; + +//@@author wm28 +/** + * Represents a supported file that can read and write AdaptedPersons + */ +public interface SupportedFile { + + /** + * Read {@code AdaptedPerson} AdaptedPerson of all types from file. + */ + List readAdaptedPersons() throws IOException; + + /** + * Write {@code AdaptedPerson} of all types to file + */ + void writeAdaptedPersons(List adaptedPersons) throws IOException; + + /** + * Returns the file format the PersonConverter supports + */ + SupportedFileFormat getSupportedFileFormat(); + + /** + * Returns the file name of the file + */ + String getFileName(); +} +//@@author diff --git a/src/main/java/seedu/address/logic/converters/fileformats/SupportedFileFormat.java b/src/main/java/seedu/address/logic/converters/fileformats/SupportedFileFormat.java new file mode 100644 index 000000000000..402579fe70dd --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/fileformats/SupportedFileFormat.java @@ -0,0 +1,27 @@ +package seedu.address.logic.converters.fileformats; + +//@@author wm28 +import java.util.Optional; + +import seedu.address.commons.util.FileUtil; + +/** + * Represents a supported file format for the import & export commands + */ +public enum SupportedFileFormat { + CSV; + + /** + * Searches SupportedFileFormat for the correct file format + */ + public static Optional findSupportedFileFormat(String fileName) { + Optional fileFormat = Optional.empty(); + for (SupportedFileFormat supportedFileFormat : SupportedFileFormat.values()) { + if (FileUtil.isValidFileExtension(fileName, supportedFileFormat.name())) { + fileFormat = Optional.of(supportedFileFormat); + } + } + return fileFormat; + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvAdaptedPerson.java b/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvAdaptedPerson.java new file mode 100644 index 000000000000..7e7030fd47ec --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvAdaptedPerson.java @@ -0,0 +1,26 @@ +package seedu.address.logic.converters.fileformats.csv; + +import seedu.address.logic.converters.fileformats.AdaptedPerson; + +//@@author wm28 +/** + * Represents a Csv-formatted person + */ +public class CsvAdaptedPerson extends AdaptedPerson { + private final String csvFormattedPerson; + + public CsvAdaptedPerson(String csvFormattedPerson) { + this.csvFormattedPerson = csvFormattedPerson; + } + + @Override + public String getFormattedString() { + return csvFormattedPerson; + } + + @Override + public boolean equals(Object other) { + return csvFormattedPerson.equals(((CsvAdaptedPerson) other).getFormattedString()); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvFile.java b/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvFile.java new file mode 100644 index 000000000000..251b63dbd90f --- /dev/null +++ b/src/main/java/seedu/address/logic/converters/fileformats/csv/CsvFile.java @@ -0,0 +1,78 @@ +package seedu.address.logic.converters.fileformats.csv; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_FILE_ALREADY_EXIST; +import static seedu.address.commons.core.Messages.MESSAGE_FILE_NOT_FOUND; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.commons.util.FileUtil; +import seedu.address.logic.converters.fileformats.AdaptedPerson; +import seedu.address.logic.converters.fileformats.SupportedFile; +import seedu.address.logic.converters.fileformats.SupportedFileFormat; + +//@@author wm28 +/** + * Represents a csv file that can read and write AdaptedPersons + */ +public class CsvFile implements SupportedFile { + private final Path fileName; + private final SupportedFileFormat supportedFileFormat = SupportedFileFormat.CSV; + + public CsvFile(String fileName) { + requireNonNull(fileName); + this.fileName = Paths.get(fileName); + } + + /** + * Reads data in file and returns csv-formatted person, {@code CsvAdaptedPerson}. + * + * @return A List of AdaptedPerson which has objects of the CsvAdaptedPerson class. + * @throws IOException if file fails to read + */ + public List readAdaptedPersons() throws IOException { + if (!FileUtil.isFileExists(fileName)) { + throw new FileNotFoundException(String.format(MESSAGE_FILE_NOT_FOUND, fileName.toAbsolutePath())); + } + String fileContent = FileUtil.readFromFile(fileName); + List dataLines = Arrays.asList(fileContent.split("\\r?\\n")); + List result = dataLines.stream() + .filter((dataLine) -> !dataLine.trim().isEmpty()) + .map(line -> new CsvAdaptedPerson(line)) + .collect(Collectors.toList()); + return result; + } + + /** + * Write data csv-formatted person, {@code CsvAdaptedPerson}, into the file. + * + * @param adaptedPersons A list of AdaptedPerson, which contains CsvAdaptedPerson. + * @throws IOException if file fails to write. + */ + public void writeAdaptedPersons(List adaptedPersons) throws IOException { + if (FileUtil.isFileExists(fileName)) { + throw new FileAlreadyExistsException(String.format(MESSAGE_FILE_ALREADY_EXIST, fileName.getFileName())); + } + FileUtil.writeToFile(fileName, adaptedPersons.stream() + .map((adaptedPerson) -> adaptedPerson.getFormattedString()) + .collect(Collectors.joining("\n"))); + } + + @Override + public SupportedFileFormat getSupportedFileFormat() { + return supportedFileFormat; + } + + @Override + public String getFileName() { + return fileName.getFileName().toString(); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..149e60b8a072 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,22 +1,26 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDANCE; 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_PAYMENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Attendance; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -31,9 +35,11 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_PAYMENT, PREFIX_ATTENDANCE, PREFIX_UID, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_PAYMENT, PREFIX_ATTENDANCE, PREFIX_UID) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -41,10 +47,12 @@ public AddCommand parse(String args) throws ParseException { 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()); + Payment payment = ParserUtil.parsePayment(argMultimap.getValue(PREFIX_PAYMENT).get()); + Attendance attendance = ParserUtil.parseAttendance(argMultimap.getValue(PREFIX_ATTENDANCE).get()); + Uid uid = ParserUtil.parseUid(argMultimap.getValue(PREFIX_UID).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, payment, attendance, uid, tagList); return new AddCommand(person); } 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..56d5371c4609 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java @@ -0,0 +1,53 @@ +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.EventStartTime; +import seedu.address.model.event.EventVenue; +import seedu.address.model.tag.Tag; + +/** + * 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 AddCommand + * and returns an AddCommand 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_DATE, PREFIX_VENUE, PREFIX_START_TIME, PREFIX_TAG); + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DATE, PREFIX_VENUE, PREFIX_START_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEventCommand.MESSAGE_USAGE)); + } + EventName eventName = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get()); + EventDate eventDate = ParserUtil.parseEventDate(argMultimap.getValue(PREFIX_DATE).get()); + EventVenue eventVenue = ParserUtil.parseEventVenue(argMultimap.getValue(PREFIX_VENUE).get()); + EventStartTime eventStartTime = ParserUtil.parseEventStartTime(argMultimap.getValue(PREFIX_START_TIME).get()); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Event event = new Event(eventName, eventDate, eventVenue, eventStartTime, tagList); + 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/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java new file mode 100644 index 000000000000..d735e6c62c2e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java @@ -0,0 +1,45 @@ +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_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new AddTagCommand object + */ +public class AddTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddTagCommand + * and returns an AddTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!AddTagCommandParser.arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE)); + } + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + return new AddTagCommand(tagList); + } + + /** + * Returns true if the tag prefix does not return 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..f8cde993a507 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,18 +7,32 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.commands.AddTagCommand; 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.EmailAllCommand; +import seedu.address.logic.commands.EmailSpecificCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.ExportCommand; +import seedu.address.logic.commands.FilterCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ImportCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MailCommand; +import seedu.address.logic.commands.MarkCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveTagCommand; import seedu.address.logic.commands.SelectCommand; import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.UnmarkCommand; + import seedu.address.logic.parser.exceptions.ParseException; /** @@ -46,43 +60,92 @@ public Command parseCommand(String userInput) throws ParseException { final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); + switch (commandWord) { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); + //@@author SandhyaGopakumar + case AddEventCommand.COMMAND_WORD: + return new AddEventCommandParser().parse(arguments); + //@@author + 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 ClearCommand.COMMAND_WORD: - return new ClearCommand(); - case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + //@@author Sarah + case FilterCommand.COMMAND_WORD: + return new FilterCommandParser().parse(arguments); + //@@author + + case ImportCommand.COMMAND_WORD: + return new ImportCommandParser().parse(arguments); + + case MarkCommand.COMMAND_WORD: + return new MarkCommandParser().parse(arguments); + + case UnmarkCommand.COMMAND_WORD: + return new UnmarkCommandParser().parse(arguments); + + case RemoveTagCommand.COMMAND_WORD: + return new RemoveTagCommandParser().parse(arguments); + + case AddTagCommand.COMMAND_WORD: + return new AddTagCommandParser().parse(arguments); + + case ExportCommand.COMMAND_WORD: + return new ExportCommandParser().parse(arguments); + + //@@author aaryamNUS + case MailCommand.COMMAND_WORD: + return new MailCommandParser().parse(arguments); + + case EmailSpecificCommand.COMMAND_WORD: + return new EmailSpecificCommandParser().parse(arguments); + + case EmailAllCommand.COMMAND_WORD: + return new EmailAllCommandParser().parse(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommandParser().parse(arguments); + + case RedoCommand.COMMAND_WORD: + return new RedoCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); case HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); + return new HistoryCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + return new ExitCommandParser().parse(arguments); case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + return new HelpCommandParser().parse(arguments); - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); + //@@author SandhyaGopakumar + case DeleteEventCommand.COMMAND_WORD: + return new DeleteEventCommandParser().parse(arguments); + //@@author - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); + //@@author aaryamNUS + case ClearCommand.COMMAND_WORD: + return new ClearCommandParser().parse(arguments); + //@@author default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/ClearCommandParser.java b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java new file mode 100644 index 000000000000..e45949e68b2b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new ClearCommand object + */ +public class ClearCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the ClearCommand + * and returns a ClearCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word clear + * @throws ParseException if the user input does not conform the expected format + */ + public ClearCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "ClearCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + ClearCommand.MESSAGE_USAGE)); + } + + return new ClearCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..40aaa0bcad9b 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,16 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + //@@author Sarah + public static final Prefix PREFIX_PAYMENT = new Prefix("pa/"); + public static final Prefix PREFIX_ATTENDANCE = new Prefix("a/"); + //@@author + //@@author SandhyaGopakumar + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_VENUE = new Prefix("v/"); + public static final Prefix PREFIX_START_TIME = new Prefix("st/"); + //@@author public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_UID = new Prefix("u/"); } 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..53b75b76f33b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new DeleteEventCommand object + */ +public class DeleteEventCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteEventCommand + * and returns a DeleteEventCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word delete_event + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteEventCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "DeleteEventCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + DeleteEventCommand.MESSAGE_USAGE)); + } + + return new DeleteEventCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea1..f85b339cc813 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -1,12 +1,15 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_EDITING_UID; 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_ATTENDANCE; 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_PAYMENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_UID; import java.util.Collection; import java.util.Collections; @@ -32,7 +35,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_PAYMENT, PREFIX_ATTENDANCE, PREFIX_UID, PREFIX_TAG); Index index; @@ -52,9 +56,19 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + //@@author Sarah + if (argMultimap.getValue(PREFIX_PAYMENT).isPresent()) { + editPersonDescriptor.setPayment(ParserUtil.parsePayment(argMultimap.getValue(PREFIX_PAYMENT).get())); } + if (argMultimap.getValue(PREFIX_ATTENDANCE).isPresent()) { + editPersonDescriptor.setAttendance(ParserUtil.parseAttendance( + argMultimap.getValue(PREFIX_ATTENDANCE).get())); + } + //@@author kronicler + if (argMultimap.getValue(PREFIX_UID).isPresent()) { + throw new ParseException(MESSAGE_EDITING_UID); + } + //@@author parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { 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..272d8f3f82a4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java @@ -0,0 +1,72 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.address.logic.commands.EditEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditCommand 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_DATE, PREFIX_VENUE, + PREFIX_START_TIME, PREFIX_TAG); + + EditEventCommand.EditEventDetails editEventDetails = new EditEventCommand.EditEventDetails(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editEventDetails.setEventName(ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editEventDetails.setEventDate(ParserUtil.parseEventDate(argMultimap.getValue(PREFIX_DATE).get())); + } + if (argMultimap.getValue(PREFIX_VENUE).isPresent()) { + editEventDetails.setEventVenue(ParserUtil.parseEventVenue(argMultimap.getValue(PREFIX_VENUE).get())); + } + if (argMultimap.getValue(PREFIX_START_TIME).isPresent()) { + editEventDetails.setEventStartTime(ParserUtil.parseEventStartTime + (argMultimap.getValue(PREFIX_START_TIME).get())); + } + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editEventDetails::setEventTags); + + if (!editEventDetails.isAnyFieldEdited()) { + throw new ParseException(EditEventCommand.MESSAGE_NOT_EDITED_EVENT); + } + + return new EditEventCommand(editEventDetails); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EmailAllCommandParser.java b/src/main/java/seedu/address/logic/parser/EmailAllCommandParser.java new file mode 100644 index 000000000000..1c94f0e92736 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EmailAllCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.logic.commands.EmailAllCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new EmailAllCommand object + */ +public class EmailAllCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the EmailAllCommand + * and returns a EmailAllCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word EmailAll + * @throws ParseException if the user input does not conform the expected format + */ + public EmailAllCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "EmailAllCommand arguments are null"); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EmailAllCommand.MESSAGE_USAGE)); + } + + return new EmailAllCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EmailSpecificCommandParser.java b/src/main/java/seedu/address/logic/parser/EmailSpecificCommandParser.java new file mode 100644 index 000000000000..599b6effe541 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EmailSpecificCommandParser.java @@ -0,0 +1,45 @@ +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_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.EmailSpecificCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new EmailSpecificCommand object + */ +public class EmailSpecificCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EmailSpecificCommand + * and returns an EmailSpecificCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EmailSpecificCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!EmailSpecificCommandParser.arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EmailSpecificCommand.MESSAGE_USAGE)); + } + + Set tagsOfGuests = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + return new EmailSpecificCommand(tagsOfGuests); + } + + /** + * Returns true if the tag prefix does not return 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/ExitCommandParser.java b/src/main/java/seedu/address/logic/parser/ExitCommandParser.java new file mode 100644 index 000000000000..155ddccd073b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ExitCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new ExitCommand object + */ +public class ExitCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the ExitCommand + * and returns a ExitCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word exit + * @throws ParseException if the user input does not conform the expected format + */ + public ExitCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "ExitCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + ExitCommand.MESSAGE_USAGE)); + } + + return new ExitCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ExportCommandParser.java b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java new file mode 100644 index 000000000000..7aae5c87b4c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java @@ -0,0 +1,54 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FILE_PATH; +import static seedu.address.commons.core.Messages.MESSAGE_UNSUPPORTED_FILE_EXTENSION; + +import java.util.Optional; + +import seedu.address.commons.util.FileUtil; +import seedu.address.logic.commands.ExportCommand; +import seedu.address.logic.converters.CsvPersonConverter; +import seedu.address.logic.converters.fileformats.SupportedFileFormat; +import seedu.address.logic.converters.fileformats.csv.CsvFile; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author wm28 +/** + * Parses input arguments and creates a new ExportCommand object + */ +public class ExportCommandParser implements Parser { + + /** + * Parses the given argument {@code String} in the context of the ExportCommand + * and returns an ExportCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ExportCommand parse(String arg) throws ParseException { + String trimmedArg = arg.trim(); + Optional fileFormat; + + if (trimmedArg.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } else if (!FileUtil.isValidPath(trimmedArg)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILE_PATH, ExportCommand.MESSAGE_USAGE)); + } + + fileFormat = SupportedFileFormat.findSupportedFileFormat(trimmedArg); + + if (fileFormat.isPresent()) { + switch (fileFormat.get()) { + case CSV: + return new ExportCommand(new CsvFile(trimmedArg), new CsvPersonConverter()); + default: + throw new ParseException(String.format(MESSAGE_UNSUPPORTED_FILE_EXTENSION, + ExportCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_UNSUPPORTED_FILE_EXTENSION, ExportCommand.MESSAGE_USAGE)); + } + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java new file mode 100644 index 000000000000..add3d0f2c49f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -0,0 +1,47 @@ +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 seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.ContainsKeywordsPredicate; + +//@@author Sarah +/** + * Parses input arguments and creates a new FilterCommand object + */ +public class FilterCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FilterCommand + * and returns an FilterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + String[] keywords = trimmedArgs.split("\\s+"); + + ArrayList checking = new ArrayList<>(Arrays.asList(keywords)); + + for (int i = 0; i < checking.size(); i++) { + + if (((checking.get(i).charAt(0) == 'p' && checking.get(i).charAt(1) == 'a') + || checking.get(i).charAt(0) == 'a' || checking.get(i).charAt(0) == 't') + && (checking.get(i).charAt(1) == '/' || checking.get(i).charAt(2) == '/')) { + + return new FilterCommand(new ContainsKeywordsPredicate(Arrays.asList(keywords))); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + } + return new FilterCommand(new ContainsKeywordsPredicate(Arrays.asList(keywords))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..77ba21069acd 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -2,6 +2,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import java.util.ArrayList; import java.util.Arrays; import seedu.address.logic.commands.FindCommand; @@ -27,6 +28,20 @@ public FindCommand parse(String args) throws ParseException { String[] nameKeywords = trimmedArgs.split("\\s+"); + ArrayList checking = new ArrayList<>(Arrays.asList(nameKeywords)); + + for (int i = 0; i < checking.size(); i++) { + + if ((checking.get(i).charAt(0) == 'n' || checking.get(i).charAt(0) == 'e' + || checking.get(i).charAt(0) == 'p') && checking.get(i).charAt(1) == '/') { + + return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java new file mode 100644 index 000000000000..a164c9213d0b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new HelpCommand object + */ +public class HelpCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the HelpCommand + * and returns a HelpCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word help + * @throws ParseException if the user input does not conform the expected format + */ + public HelpCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "HelpCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE)); + } + + return new HelpCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/HistoryCommandParser.java b/src/main/java/seedu/address/logic/parser/HistoryCommandParser.java new file mode 100644 index 000000000000..eb7d62c76a5c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/HistoryCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new HistoryCommand object + */ +public class HistoryCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the HistoryCommand + * and returns a HistoryCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word history + * @throws ParseException if the user input does not conform the expected format + */ + public HistoryCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "HistoryCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + HistoryCommand.MESSAGE_USAGE)); + } + + return new HistoryCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ImportCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java new file mode 100644 index 000000000000..9dd5d134db73 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FILE_PATH; +import static seedu.address.commons.core.Messages.MESSAGE_UNSUPPORTED_FILE_EXTENSION; + +import java.util.Optional; + +import seedu.address.commons.util.FileUtil; +import seedu.address.logic.commands.ImportCommand; +import seedu.address.logic.converters.CsvPersonConverter; +import seedu.address.logic.converters.fileformats.SupportedFileFormat; +import seedu.address.logic.converters.fileformats.csv.CsvFile; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author wm28 + +/** + * Parses input arguments and creates a new ImportCommand object + */ +public class ImportCommandParser implements Parser { + + /** + * Parses the given argument {@code String} in the context of the ImportCommand + * and returns an ImportCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ImportCommand parse(String arg) throws ParseException { + String trimmedArg = arg.trim(); + Optional fileFormat; + + if (trimmedArg.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } else if (!FileUtil.isValidPath(trimmedArg)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILE_PATH, ImportCommand.MESSAGE_USAGE)); + } + + fileFormat = SupportedFileFormat.findSupportedFileFormat(trimmedArg); + + if (fileFormat.isPresent()) { + switch (fileFormat.get()) { + case CSV: + return new ImportCommand(new CsvFile(trimmedArg), new CsvPersonConverter()); + default: + throw new ParseException(String.format(MESSAGE_UNSUPPORTED_FILE_EXTENSION, + ImportCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_UNSUPPORTED_FILE_EXTENSION, ImportCommand.MESSAGE_USAGE)); + } + } +} +//@@author 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..5a21ef86a1f2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns a ListCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word list + * @throws ParseException if the user input does not conform the expected format + */ + public ListCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "ListCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + ListCommand.MESSAGE_USAGE)); + } + + return new ListCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/MailCommandParser.java b/src/main/java/seedu/address/logic/parser/MailCommandParser.java new file mode 100644 index 000000000000..40eb5bd5cffc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MailCommandParser.java @@ -0,0 +1,36 @@ +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.MailCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MailCommand object + */ +//@@author aaryamNUS +public class MailCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MailCommand + * and returns a MailCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MailCommand parse(String args) throws ParseException { + //ensure the arguments are not empty + assert args != null; + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args); + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MailCommand.MESSAGE_USAGE), pe); + } + + return new MailCommand(index); + } +} diff --git a/src/main/java/seedu/address/logic/parser/MarkCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java new file mode 100644 index 000000000000..6174e6c89eab --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; +//@@author kronicler +/** + * Parses input arguments and creates a new EditCommand object + */ + +public class MarkCommandParser 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 MarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args); + + Uid uid; + + try { + uid = ParserUtil.parseUid(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE), pe); + } + + return new MarkCommand(uid); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3ff..73673a7ae3fb 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -13,4 +13,5 @@ public interface Parser { * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; + } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..31b0c5e21815 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,10 +9,16 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.EventStartTime; +import seedu.address.model.event.EventVenue; +import seedu.address.model.person.Attendance; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -65,21 +71,23 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } + //@@author Sarah /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String attendance} into an {@code attendance}. * Leading and trailing whitespaces will be trimmed. * * @throws ParseException if the given {@code address} is invalid. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + public static Attendance parseAttendance(String attendance) throws ParseException { + requireNonNull(attendance); + String trimmedAttendance = attendance.trim(); + if (!Attendance.isValidAttendance(trimmedAttendance)) { + throw new ParseException(Attendance.MESSAGE_ATTENDANCE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Attendance(trimmedAttendance); } + //@@author /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. @@ -95,6 +103,38 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + //@@author Sarah + /** + * Parses a {@code String Payment} into an {@code Payment}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code payment} is invalid. + */ + public static Payment parsePayment(String payment) throws ParseException { + requireNonNull(payment); + String trimmedPayment = payment.trim(); + if (!Payment.isValidPayment(trimmedPayment)) { + throw new ParseException(Payment.MESSAGE_PAYMENT_CONSTRAINTS); + } + return new Payment(trimmedPayment); + } + + //@@author + /** + * Parses a {@code String Payment} into an {@code Payment}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code payment} is invalid. + */ + public static Uid parseUid(String uid) throws ParseException { + requireNonNull(uid); + String trimmedUid = uid.trim(); + if (!Uid.isValidUid(trimmedUid)) { + throw new ParseException(Uid.MESSAGE_UID_CONSTRAINTS); + } + return new Uid(trimmedUid); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +161,52 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses {@code String EventName} into an {@code EventName}. + */ + public static EventName parseEventName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!EventName.isValidEventName(trimmedName)) { + throw new ParseException(EventName.MESSAGE_EVENTNAME_CONSTRAINTS); + } + return new EventName(trimmedName); + } + + /** + * Parses {@code String EventDate} into an {@code EventDate}. + */ + public static EventDate parseEventDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + if (!EventDate.isValidEventDate(trimmedDate)) { + throw new ParseException(EventDate.MESSAGE_EVENTDATE_CONSTRAINTS); + } + return new EventDate(trimmedDate); + } + + /** + * Parses {@code String EventVenue} into an {@code EventVenue}. + */ + public static EventVenue parseEventVenue(String venue) throws ParseException { + requireNonNull(venue); + String trimmedVenue = venue.trim(); + if (!EventVenue.isValidEventVenue(trimmedVenue)) { + throw new ParseException(EventVenue.MESSAGE_EVENTVENUE_CONSTRAINTS); + } + return new EventVenue(trimmedVenue); + } + + /** + * Parses {@code String EventStartTime} into an {@code EventStartTime}. + */ + public static EventStartTime parseEventStartTime(String startTime) throws ParseException { + requireNonNull(startTime); + String trimmedStartTime = startTime.trim(); + if (!EventStartTime.isValidEventStartTime(trimmedStartTime)) { + throw new ParseException(EventStartTime.MESSAGE_EVENTSTARTTIME_CONSTRAINTS); + } + return new EventStartTime(trimmedStartTime); + } } diff --git a/src/main/java/seedu/address/logic/parser/RedoCommandParser.java b/src/main/java/seedu/address/logic/parser/RedoCommandParser.java new file mode 100644 index 000000000000..c0380fdbe02b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RedoCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new RedoCommand object + */ +public class RedoCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the RedoCommand + * and returns a RedoCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word redo + * @throws ParseException if the user input does not conform the expected format + */ + public RedoCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "RedoCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + RedoCommand.MESSAGE_USAGE)); + } + + return new RedoCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java new file mode 100644 index 000000000000..0904de0fed90 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java @@ -0,0 +1,45 @@ +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_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new RemoveTagCommand object + */ +public class RemoveTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveTagCommand + * and returns an RemoveTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!RemoveTagCommandParser.arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveTagCommand.MESSAGE_USAGE)); + } + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + return new RemoveTagCommand(tagList); + } + + /** + * Returns true if the tag prefix does not return 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/UndoCommandParser.java b/src/main/java/seedu/address/logic/parser/UndoCommandParser.java new file mode 100644 index 000000000000..1f54d8f4c243 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UndoCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author aaryamNUS +/** + * Parses input arguments and creates a new UndoCommand object + */ +public class UndoCommandParser implements Parser { + private static Logger logger = Logger.getLogger("parse"); + + /** + * Parses the given {@code String} of arguments in the context of the UndoCommand + * and returns an UndoCommand object for execution. For this command, parser needs to + * ensure that the arguments are null, i.e. no extra characters are inputted after the + * command word undo + * @throws ParseException if the user input does not conform the expected format + */ + public UndoCommand parse(String args) throws ParseException { + //need to ensure that the arguments are indeed null + + if (args == null || args.replaceAll("\\s+", "").equals("")) { + logger.log(Level.INFO, "UndoCommand arguments are null"); + } else { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + UndoCommand.MESSAGE_USAGE)); + } + + return new UndoCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java new file mode 100644 index 000000000000..f3799b54f39d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.UnmarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Uid; + +//@@author kronicler +/** + * Parses input arguments and creates a new EditCommand object + */ +public class UnmarkCommandParser 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 UnmarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args); + + Uid uid; + + try { + uid = ParserUtil.parseUid(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE), pe); + } + + return new UnmarkCommand(uid); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 7f85c8b9258b..24a536669a94 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -2,11 +2,15 @@ import static java.util.Objects.requireNonNull; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.tag.Tag; /** * Wraps all data at the address-book level @@ -15,7 +19,9 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; - + //@@author SandhyaGopakumar + private final Event eventDetails; + //@@author /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html @@ -25,6 +31,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + eventDetails = new Event(); } public AddressBook() {} @@ -47,15 +54,43 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + //@@author SandhyaGopakumar + + /** + * Replaces the current details of the event with {@code event}. + */ + public void setEvent(Event event) { + this.eventDetails.setEvent(event); + } + //@@author + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setEvent(newData.getEventDetails()); + } + + //@@author SandhyaGopakumar + //event-level operations + /** Adds the details given by the user to event. */ + public void addEvent(Event e) { + eventDetails.addEvent(e); + } + + /** Deletes the event details stored in the addressbook. */ + public void deleteEvent() { + eventDetails.deleteEvent(); } + /** Returns true if the event details given by the user is being stored in the addressbook. */ + public boolean hasEvent() { + return eventDetails.isUserInitialised(); + } + //@@author + //// person-level operations /** @@ -93,8 +128,69 @@ public void removePerson(Person key) { persons.remove(key); } + //@@author kronicler + /** + * Returns true if a person with the same UID as {@code person} exists in the address book. + */ + public boolean hasUid(Person person) { + requireNonNull(person); + return persons.checkUid(person); + } + //@@author + //// util methods + //@@author aaryamNUS + /** + * Removes {@code tag} from {@code person} in this {@code AddressBook}. + * Note: This code snippet was inspired from the PR "Model: Add deleteTag(Tag)" by @yamgent + */ + private void removeTagFromPerson(Tag tag, Person person) { + Set newTags = new HashSet<>(person.getTags()); + + if (!newTags.remove(tag)) { + return; + } + + Person newPerson = new Person (person.getName(), person.getPhone(), person.getEmail(), + person.getPayment(), person.getAttendance(), person.getUid(), newTags); + + updatePerson(person, newPerson); + } + + /** + * Removes {@code tag} from all persons in this {@code AddressBook} + */ + public void removeTag(Tag tag) { + persons.forEach(person -> removeTagFromPerson(tag, person)); + } + + /** + * Adds {@code tag} from {@code person} in this {@code AddressBook}. + * Note: This code snippet was inspired from the PR "Model: Add deleteTag(Tag)" by @yamgent from SE-EDU + */ + private void addTagFromPerson(Tag tag, Person person) { + Set newTags = new HashSet<>(person.getTags()); + + if (!newTags.add(tag)) { + return; + } + + Person newPerson = + new Person (person.getName(), person.getPhone(), person.getEmail(), person.getPayment(), + person.getAttendance(), person.getUid(), newTags); + + updatePerson(person, newPerson); + } + + /** + * Adds {@code tag} to all persons in this {@code AddressBook} + */ + public void addTag(Tag tag) { + persons.forEach(person -> addTagFromPerson(tag, person)); + } + //@@author aaryamNUS + @Override public String toString() { return persons.asUnmodifiableObservableList().size() + " persons"; @@ -106,6 +202,11 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public Event getEventDetails() { + return eventDetails; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..b0ce062afa83 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,7 +3,9 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * The API of the Model component. @@ -17,6 +19,31 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + //@@author SandhyaGopakumar + /** + * Returns true if an event with the same identity as {@code event} exists in the application. + */ + boolean hasEvent(); + + /** + * Deletes the given event. + * An event must have been initialised by the user in the application. + */ + void deleteEvent(); + + /** + * Adds the given event. + * {@code event} with details given by the user must not already exist in the application. + */ + void addEvent(Event event); + + /** + * Replaces the existing event with {@code editedEvent}. + * The existing event must have been initialised by the user. + * The event details of {@code editedEvent} must not be the same as the existing event. + */ + void updateEvent(Event editedEvent); + //@@author /** * Returns true if a person with the same identity as {@code person} exists in the address book. @@ -42,15 +69,40 @@ public interface Model { */ void updatePerson(Person target, Person editedPerson); + //@@author aaryamNUS + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + boolean hasUid(Person person); + + /** + * Removes the given {@code tag} from all {@code Person}s + */ + void deleteTag(Tag tag); + + /** + * Adds the given {@code tag} to all {@code Person}s + */ + void addTag(Tag tag); + //@@author + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + //@@author SandhyaGopakumar + /** Returns the details of the event currently residing in the addressbook. */ + Event getEventDetails(); + //@@author + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + /** + * Returns true if the model has previous address book states to restore. + */ /** * Returns true if the model has previous address book states to restore. */ diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..23e9a9fb9b3d 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,7 +12,9 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * Represents the in-memory model of the address book data. @@ -22,7 +24,6 @@ public class ModelManager extends ComponentManager implements Model { private final VersionedAddressBook versionedAddressBook; private final FilteredList filteredPersons; - /** * Initializes a ModelManager with the given addressBook and userPrefs. */ @@ -56,6 +57,37 @@ private void indicateAddressBookChanged() { raise(new AddressBookChangedEvent(versionedAddressBook)); } + //@@author SandhyaGopakumar + @Override + public void addEvent(Event event) { + versionedAddressBook.addEvent(event); + indicateAddressBookChanged(); + } + + @Override + public void deleteEvent() { + versionedAddressBook.deleteEvent(); + indicateAddressBookChanged(); + } + + @Override + public boolean hasEvent() { + return versionedAddressBook.hasEvent(); + } + + @Override + public void updateEvent(Event editedEvent) { + versionedAddressBook.setEvent(editedEvent); + indicateAddressBookChanged(); + } + + /** Returns the details of the event currently residing in the addressbook. */ + @Override + public Event getEventDetails() { + return versionedAddressBook.getEventDetails(); + } + //@@author + @Override public boolean hasPerson(Person person) { requireNonNull(person); @@ -83,6 +115,24 @@ public void updatePerson(Person target, Person editedPerson) { indicateAddressBookChanged(); } + //@@author kronicler + @Override + public boolean hasUid(Person person) { + requireNonNull(person); + return versionedAddressBook.hasUid(person); + } + //@@author + + @Override + public void deleteTag(Tag tag) { + versionedAddressBook.removeTag(tag); + } + + @Override + public void addTag(Tag tag) { + versionedAddressBook.addTag(tag); + } + //=========== Filtered Person List Accessors ============================================================= /** diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a290..86dfa24beda9 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -13,5 +14,8 @@ public interface ReadOnlyAddressBook { * This list will not contain any duplicate persons. */ ObservableList getPersonList(); + //@@author SandhyaGopakumar + Event getEventDetails(); + //@@author } 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..40b3da01fbb4 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Event.java @@ -0,0 +1,213 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + +/** + * Represents an event. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Event { + + //Identity fields + private EventName eventName; + private EventDate eventDate; + private EventVenue eventVenue; + private EventStartTime eventStartTime; + private Set eventTags = new HashSet<>(); + private boolean isNotInitialisedByUser; + /** + * Every field must be present and not null. + */ + public Event(EventName eventName, EventDate eventDate, EventVenue eventVenue, + EventStartTime eventStartTime, Set eventTags) { + requireAllNonNull(eventName, eventDate, eventVenue, eventStartTime, eventTags); + this.eventName = eventName; + this.eventDate = eventDate; + this.eventVenue = eventVenue; + this.eventStartTime = eventStartTime; + this.eventTags.addAll(eventTags); + this.isNotInitialisedByUser = false; + } + public Event(EventName eventName, EventDate eventDate, EventVenue eventVenue, + EventStartTime eventStartTime, Set eventTags, Boolean eventIsNotInitialisedByUser) { + requireAllNonNull(eventName, eventDate, eventVenue, eventStartTime, eventTags, eventIsNotInitialisedByUser); + this.eventName = eventName; + this.eventDate = eventDate; + this.eventVenue = eventVenue; + this.eventStartTime = eventStartTime; + this.eventTags.addAll(eventTags); + this.isNotInitialisedByUser = eventIsNotInitialisedByUser; + } + public Event() { + EventName eventName = new EventName("event not created yet"); + this.eventName = eventName; + EventDate eventDate = new EventDate("1/10/2019"); + this.eventDate = eventDate; + EventVenue eventVenue = new EventVenue("NA"); + this.eventVenue = eventVenue; + EventStartTime eventStartTime = new EventStartTime("1:00 pm"); + this.eventStartTime = eventStartTime; + Tag tag = new Tag("NA"); + this.eventTags.add(tag); + this.isNotInitialisedByUser = true; + } + + public String getName() { + return eventName.getEventName(); + } + + public String getDate() { + return eventDate.getEventDate(); + } + + public Date getFullDate() { + return eventDate.getFullEventDate(); + } + + public String getVenue() { + return eventVenue.getEventVenue(); + } + + public String getStartTime() { + return eventStartTime.getEventStartTime(); + } + + private EventName getEventName() { + return eventName; + } + + private EventDate getEventDate() { + return eventDate; + } + + private EventVenue getEventVenue() { + return eventVenue; + } + + private EventStartTime getEventStartTime() { + return eventStartTime; + } + + public void setEvent(Event event) { + if (!this.equals(event)) { + this.eventName.setEventName(event.getName()); + this.eventDate.setEventDate(event.getDate()); + this.eventVenue.setEventVenue(event.getVenue()); + this.eventStartTime.setEventStartTime(event.getStartTime()); + this.eventTags = event.eventTags; + this.isNotInitialisedByUser = !event.isUserInitialised(); + } + } + + /** Adds user-given details of the event. */ + public void addEvent(Event event) { + if (!this.equals(event)) { + this.eventName.setEventName(event.getName()); + this.eventDate.setEventDate(event.getDate()); + this.eventVenue.setEventVenue(event.getVenue()); + this.eventStartTime.setEventStartTime(event.getStartTime()); + this.eventTags = event.eventTags; + } + this.isNotInitialisedByUser = false; + } + /** Deletes user-given details of the event. */ + public void deleteEvent() { + Event event = new Event(); + this.eventName.setEventName(event.getName()); + this.eventDate.setEventDate(event.getDate()); + this.eventVenue.setEventVenue(event.getVenue()); + this.eventStartTime.setEventStartTime(event.getStartTime()); + this.eventTags = event.eventTags; + this.isNotInitialisedByUser = true; + } + + public boolean isUserInitialised() { + return !isNotInitialisedByUser; + } + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getEventTags() { + return Collections.unmodifiableSet(eventTags); + } + + public long getDaysLeft() { + LocalDate eventDate = this.getFullDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate now = LocalDate.now(); + final long numberOfDaysLeft = ChronoUnit.DAYS.between(now, eventDate); + return numberOfDaysLeft; + } + + /** + * Returns true if both events of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two events. + */ + public boolean isSameEvent(seedu.address.model.event.Event otherEvent) { + if (otherEvent == this) { + return true; + } + + return otherEvent != null + && otherEvent.getName().equals(getName()) + && otherEvent.getDate().equals(getDate()) + && otherEvent.getVenue().equals(getVenue()) + && otherEvent.getStartTime().equals(getStartTime()) + && otherEvent.getEventTags().equals(getEventTags()); + } + + /** + * 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 seedu.address.model.event.Event)) { + return false; + } + + seedu.address.model.event.Event otherEvent = (seedu.address.model.event.Event) other; + return otherEvent.getEventName().equals(getEventName()) + && otherEvent.getEventDate().equals(getEventDate()) + && otherEvent.getEventVenue().equals(getEventVenue()) + && otherEvent.getEventStartTime().equals(getEventStartTime()) + && otherEvent.getEventTags().equals(getEventTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(eventName, eventDate, eventVenue, eventStartTime, eventTags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Date: ") + .append(getDate()) + .append(" Venue: ") + .append(getVenue()) + .append(" Start Time: ") + .append(getStartTime()) + .append(" Tags: "); + getEventTags().forEach(builder::append); + return builder.toString(); + } + +} 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..8b0abf9696cb --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventDate.java @@ -0,0 +1,101 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +/** + * Represents a Event's date. + * Guarantees: immutable; is valid as declared in {@link #isValidEventDate(String)} + */ +public class EventDate { + + //Identity fields + public static final String MESSAGE_EVENTDATE_CONSTRAINTS = + "Event's date should be valid, contain only numbers and " + + "forward slash and should follow the dd/mm/yyyy format."; + + private String fullEventDate; + + /** + * Constructs a {@code eventDate}. + * + * @param eventDate A valid event date. + */ + public EventDate(String eventDate) { + requireNonNull(eventDate); + checkArgument(isValidEventDate(eventDate), MESSAGE_EVENTDATE_CONSTRAINTS); + fullEventDate = eventDate; + } + + /** + * Accessor method for eventDate + */ + public String getEventDate() { + return this.fullEventDate; + } + /** + * Setter method for eventDate + */ + public void setEventDate(String eventDate) { + this.fullEventDate = eventDate; + } + + public Date getFullEventDate() { + SimpleDateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy"); + dateFormatter.setLenient(false); + Date eventDate = null; + try { + eventDate = dateFormatter.parse(this.fullEventDate); + } catch (Exception e) { + eventDate = null; + } + return eventDate; + } + /** + * Returns true if a given string is a valid event date. + */ + public static boolean isValidEventDate(String test) { + requireNonNull(test); + SimpleDateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy"); + dateFormatter.setLenient(false); + Date eventDate = null; + try { + eventDate = dateFormatter.parse(test); + } catch (Exception e) { + return false; + } + LocalDate eventLocalDate = eventDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate now = LocalDate.now(); + final long numberOfDaysLeft = ChronoUnit.DAYS.between(now, eventLocalDate); + if (numberOfDaysLeft < 0) { + return false; + } else { + return true; + } + } + + + @Override + public String toString() { + return fullEventDate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventDate // instanceof handles nulls + && fullEventDate.equals(((EventDate) other).fullEventDate)); // state check + } + + @Override + public int hashCode() { + return fullEventDate.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..8f425bf9eaaf --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventName.java @@ -0,0 +1,69 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's name. + * Guarantees: immutable; is valid as declared in {@link #isValidEventName(String)} + */ +//@@author SandhyaGopakumar +public class EventName { + + //Identity fields + public static final String MESSAGE_EVENTNAME_CONSTRAINTS = + "Event names should only contain alphanumeric characters and spaces, and it should not be blank"; + + public static final String EVENTNAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ']*"; + + private String fullEventName; + + /** + * Constructs a {@code eventName}. + * + * @param eventName A valid event name. + */ + public EventName(String eventName) { + requireNonNull(eventName); + checkArgument(isValidEventName(eventName), MESSAGE_EVENTNAME_CONSTRAINTS); + fullEventName = eventName; + } + + /** + * Accessor method for eventName + */ + public String getEventName() { + return this.fullEventName; + } + /** + * Setter method for eventName + */ + public void setEventName(String eventName) { + this.fullEventName = eventName; + } + /** + * Returns true if a given string is a valid event name. + */ + public static boolean isValidEventName(String test) { + return test.matches(EVENTNAME_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullEventName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventName // instanceof handles nulls + && fullEventName.equals(((EventName) other).fullEventName)); // state check + } + + @Override + public int hashCode() { + return fullEventName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/EventStartTime.java b/src/main/java/seedu/address/model/event/EventStartTime.java new file mode 100644 index 000000000000..9d3c8b999e0b --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventStartTime.java @@ -0,0 +1,69 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's start time. + * Guarantees: immutable; is valid as declared in {@link #isValidEventStartTime(String)} + */ +public class EventStartTime { + + //Identity fields + public static final String MESSAGE_EVENTSTARTTIME_CONSTRAINTS = + "Event's start time should only contain alphanumeric characters " + + "and spaces in the 12 hour format and should not be blank."; + + public static final String EVENTSTARTTIME_VALIDATION_REGEX = "(1[012]|[1-9]):[0-5][0-9](\\s)?(?i)(am|pm)"; + + private String fullEventStartTime; + + /** + * Constructs a {@code eventStartTime}. + * + * @param eventStartTime A valid event start time. + */ + public EventStartTime(String eventStartTime) { + requireNonNull(eventStartTime); + checkArgument(isValidEventStartTime(eventStartTime), MESSAGE_EVENTSTARTTIME_CONSTRAINTS); + fullEventStartTime = eventStartTime; + } + + /** + * Accessor method for eventStartTime + */ + public String getEventStartTime() { + return this.fullEventStartTime; + } + /** + * Setter method for eventStartTime + */ + public void setEventStartTime(String eventStartTime) { + this.fullEventStartTime = eventStartTime; + } + /** + * Returns true if a given string is a valid event start time. + */ + public static boolean isValidEventStartTime(String test) { + return test.matches(EVENTSTARTTIME_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullEventStartTime; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventStartTime // instanceof handles nulls + && fullEventStartTime.equals(((EventStartTime) other).fullEventStartTime)); // state check + } + + @Override + public int hashCode() { + return fullEventStartTime.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/EventVenue.java b/src/main/java/seedu/address/model/event/EventVenue.java new file mode 100644 index 000000000000..8e2ac6e819f0 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventVenue.java @@ -0,0 +1,68 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's venue. + * Guarantees: immutable; is valid as declared in {@link #isValidEventVenue(String)} + */ +public class EventVenue { + + //Identity fields + public static final String MESSAGE_EVENTVENUE_CONSTRAINTS = + "Event's venue should only contain alphanumeric characters, spaces, ',' or '#' and should not be blank."; + + public static final String EVENTVENUE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ,#-]*"; + + private String fullEventVenue; + + /** + * Constructs a {@code eventVenue}. + * + * @param eventVenue A valid event venue. + */ + public EventVenue(String eventVenue) { + requireNonNull(eventVenue); + checkArgument(isValidEventVenue(eventVenue), MESSAGE_EVENTVENUE_CONSTRAINTS); + fullEventVenue = eventVenue; + } + + /** + * Accessor method for eventVenue + */ + public String getEventVenue() { + return this.fullEventVenue; + } + /** + * Setter method for eventVenue + */ + public void setEventVenue(String eventVenue) { + this.fullEventVenue = eventVenue; + } + /** + * Returns true if a given string is a valid event venue. + */ + public static boolean isValidEventVenue(String test) { + return test.matches(EVENTVENUE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullEventVenue; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventVenue // instanceof handles nulls + && fullEventVenue.equals(((EventVenue) other).fullEventVenue)); // state check + } + + @Override + public int hashCode() { + return fullEventVenue.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java new file mode 100644 index 000000000000..d760103168a9 --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java @@ -0,0 +1,12 @@ +package seedu.address.model.event.exceptions; + +//@@author SandhyaGopakumar +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateEventException extends RuntimeException { + public DuplicateEventException() { + super("Operation would result in duplicate events"); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java new file mode 100644 index 000000000000..f5b0a242a399 --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java @@ -0,0 +1,8 @@ +package seedu.address.model.event.exceptions; + +//@@author SandhyaGopakumar +/** + * Signals that the operation is unable to find the specified event. + */ + +public class EventNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses 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 ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Attendance.java b/src/main/java/seedu/address/model/person/Attendance.java new file mode 100644 index 000000000000..1df3b00eba35 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Attendance.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@ author Sarah +/** + * Represents a Person's attendance in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidAttendance(String)} + */ +public class Attendance { + + public static final String MESSAGE_ATTENDANCE_CONSTRAINTS = + "Attendance should only contain alphanumeric characters, spaces and '.', " + + "and it should not be blank.\n" + + " The following words are accepted (ignoring case): \"ABSENT\", \"PRESENT\", \"N.A.\"\n" + + " Any words besides these will not be accepted and a blank field will be seen."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ATTENDANCE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum}.-]*"; + + public final String attendanceValue; + + /** + * Constructs a {@code Attendance}. + * + * @param attendance A valid attendance. + */ + public Attendance(String attendance) { + requireNonNull(attendance); + checkArgument(isValidAttendance(attendance), MESSAGE_ATTENDANCE_CONSTRAINTS); + attendanceValue = attendance; + } + + /** + * Returns true if a given string is a valid attendance. + */ + public static boolean isValidAttendance(String test) { + if ((test.equalsIgnoreCase("ABSENT") + || test.equalsIgnoreCase("PRESENT") + || test.equalsIgnoreCase("N.A.")) + && test.matches(ATTENDANCE_VALIDATION_REGEX)) { + return true; + } + + return false; + } + + + @Override + public String toString() { + return attendanceValue; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Attendance // instanceof handles nulls + && attendanceValue.equals(((Attendance) other).attendanceValue)); // state check + } + + @Override + public int hashCode() { + return attendanceValue.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java new file mode 100644 index 000000000000..1d66d1c6b54e --- /dev/null +++ b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java @@ -0,0 +1,77 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.tag.Tag; + +//@@author Sarah +/** + * Tests that a {@code Person}'s {@code payment, attendance and tags etc.} matches all of the keywords given. + */ +public class ContainsKeywordsPredicate implements Predicate { + private final List keywords; + private final ArrayList checkKeywords = new ArrayList<>(); + + public ContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + /** + * + * @param person containing details such as + * payment status, attendance status and tags + * @return the details that match keywords in the person's details, as mentioned above + */ + @Override + public boolean test(Person person) { + HashSet set = new HashSet<>(person.getTags()); + String strTags = ""; + + int j = 0; + + checkKeywords.clear(); + + for (int i = 0; i < keywords.size(); i++) { + String str = keywords.get(i); + String[] arrStr = str.split("/"); + + if (arrStr[j].equals("pa")) { + checkKeywords.add(i, arrStr[j + 1]); + + strTags += " "; + strTags += person.getPayment(); + } else if (arrStr[j].equals("a")) { + checkKeywords.add(i, arrStr[j + 1]); + + strTags += " "; + strTags += person.getAttendance(); + } else if (arrStr[j].equals("t")) { + checkKeywords.add(i, arrStr[j + 1]); + + strTags = ""; + + for (Tag tag : set) { + strTags += " "; + strTags += tag.tagName; + } + } + + } + + final String checkStr = strTags; + + return checkKeywords.stream() + .allMatch(checkKeywords -> StringUtil.containsWordIgnoreCase(checkStr, checkKeywords)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index c9b5868427ca..4b9a2852fd25 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -1,15 +1,18 @@ package seedu.address.model.person; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import seedu.address.commons.util.StringUtil; +//@@author Sarah /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Person}'s {@code Name, Phone or Email} matches any of the keywords given. */ public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; + private final ArrayList checkKeywords = new ArrayList<>(); public NameContainsKeywordsPredicate(List keywords) { this.keywords = keywords; @@ -17,8 +20,38 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + String strToCheck = ""; + + int j = 0; + + checkKeywords.clear(); + + for (int i = 0; i < keywords.size(); i++) { + String str = keywords.get(i); + String[] arrStr = str.split("/"); + + if (arrStr[j].equals("n")) { + checkKeywords.add(i, arrStr[j + 1]); + + strToCheck += " "; + strToCheck += person.getName(); + } else if (arrStr[j].equals("p")) { + checkKeywords.add(i, arrStr[j + 1]); + + strToCheck += " "; + strToCheck += person.getPhone(); + } else if (arrStr[j].equals("e")) { + checkKeywords.add(i, arrStr[j + 1]); + + strToCheck += " "; + strToCheck += person.getEmail(); + } + } + + final String checkStr = strToCheck; + + return checkKeywords.stream() + .anyMatch(checkKeywords -> StringUtil.containsWordIgnoreCase(checkStr, checkKeywords)); } @Override diff --git a/src/main/java/seedu/address/model/person/Payment.java b/src/main/java/seedu/address/model/person/Payment.java new file mode 100644 index 000000000000..82a8a07b83bd --- /dev/null +++ b/src/main/java/seedu/address/model/person/Payment.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author Sarah +/** + * Represents a Person's payment in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidPayment(String)} + */ +public class Payment { + public static final String MESSAGE_PAYMENT_CONSTRAINTS = + "Payment should only contain alphanumeric characters and . such as N.A. , " + + ", it should not be blank and should not have spaces.\n" + + " The following words are accepted (ignoring case): " + + "\"PAID\", \"NOTPAID\", \"PENDING\", \"N.A.\" \n" + + " Any words besides these will not be accepted and a blank field will be seen."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String PAYMENT_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum}.-]*"; + + public final String paymentValue; + + /** + * Constructs a {@code Payment}. + * + * @param payment A valid payment. + */ + public Payment(String payment) { + requireNonNull(payment); + checkArgument(isValidPayment(payment), MESSAGE_PAYMENT_CONSTRAINTS); + paymentValue = payment; + } + + /** + * Returns true if a given string is a valid attendance. + */ + public static boolean isValidPayment(String test) { + if ((test.equalsIgnoreCase("PAID") + || test.equalsIgnoreCase("NOTPAID") + || test.equalsIgnoreCase("N.A.") + || test.equalsIgnoreCase("PENDING")) + && test.matches(PAYMENT_VALIDATION_REGEX)) { + return true; + } + return false; + } + + + @Override + public String toString() { + return paymentValue; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Payment // instanceof handles nulls + && paymentValue.equals(((Payment) other).paymentValue)); // state check + } + + @Override + public int hashCode() { + return paymentValue.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 557a7a60cd51..d6b22b1802ca 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -21,18 +21,22 @@ public class Person { private final Email email; // Data fields - private final Address address; + private final Payment payment; + private final Attendance attendance; + private final Uid uid; private final Set tags = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Payment payment, Attendance attendance, Uid uid, Set tags) { + requireAllNonNull(name, phone, email, payment, attendance, uid, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.payment = payment; + this.attendance = attendance; + this.uid = uid; this.tags.addAll(tags); } @@ -48,10 +52,22 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + //@@author Sarah + public Payment getPayment() { + return payment; } + public Attendance getAttendance() { + return attendance; + } + //@@author + + //@@author kronicler + public Uid getUid() { + return uid; + } + //@@author + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -73,7 +89,18 @@ public boolean isSamePerson(Person otherPerson) { && otherPerson.getName().equals(getName()) && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); } - + //@@author kronicler + /** + * Returns true if both persons have the same UID. This is a method to prevent UID from being non unique. + * This defines a weaker notion of equality between two persons. + */ + public boolean hasSameUid(Person otherPerson) { + if (otherPerson.getUid().equals(this.getUid())) { + return true; + } + return false; + } + //@@author /** * Returns true if both persons have the same identity and data fields. * This defines a stronger notion of equality between two persons. @@ -92,14 +119,16 @@ public boolean equals(Object other) { return otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) + && otherPerson.getPayment().equals(getPayment()) + && otherPerson.getAttendance().equals(getAttendance()) + && otherPerson.getUid().equals(getUid()) && otherPerson.getTags().equals(getTags()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, payment, attendance, tags); } @Override @@ -110,8 +139,12 @@ public String toString() { .append(getPhone()) .append(" Email: ") .append(getEmail()) - .append(" Address: ") - .append(getAddress()) + .append(" Payment: ") + .append(getPayment()) + .append(" Attendance: ") + .append(getAttendance()) + .append(" UID: ") + .append(getUid()) .append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/model/person/Uid.java b/src/main/java/seedu/address/model/person/Uid.java new file mode 100644 index 000000000000..2677336d0e05 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Uid.java @@ -0,0 +1,60 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author kronicler +/** + * Represents a Person's UID in the guest list. + * Guarantees: immutable; is valid as declared in {@link #isValidUid(String)} + */ +public class Uid { + public static final String MESSAGE_UID_CONSTRAINTS = + "UID should only contain alphanumeric characters, and should be between 5 to 20 characters long.\n" + + "Auto-generate UID: u/00000 , e.g u/00000 -> UID: 415670 (randomly-generated)\n" + + "User defined UID: u/ + NOT 00000, e.g u/00001 -> UID: 00001"; + + /* + * Ensures that only a string of alpha numeric characters are accepted and they are between 5 to 20 characters long + */ + public static final String UID_VALIDATION_REGEX = "\\p{Alnum}{5,20}"; + + public final String uidValue; + + /** + * Constructs a {@code Uid}. + * + * @param uid is a string of numbers that the Person holds. + */ + public Uid(String uid) { + requireNonNull(uid); + checkArgument(isValidUid(uid), MESSAGE_UID_CONSTRAINTS); + uidValue = uid; + } + + /** + * Returns true if a given string is a valid Uid. + */ + public static boolean isValidUid(String test) { + return test.matches(UID_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return uidValue; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Uid // instance of handles nulls + && uidValue.equals(((Uid) other).uidValue)); // state check + } + + @Override + public int hashCode() { + return uidValue.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 5856aa42e6b5..25dd927be2d8 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -34,6 +34,16 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + //@@author kronicler + /** + * Returns true if the list contains a person with the same UID as the given argument. + */ + public boolean checkUid(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::hasSameUid); + } + //@@author + /** * Adds a person to the list. * The person must not already exist in the list. diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 8cdff2773ac9..23d801409d23 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -9,7 +9,9 @@ */ public class Tag { - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric " + + "should not have special characters" + + " and should not have spaces in between words."; public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..62cbdb65e9c3 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,11 +6,13 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; +import seedu.address.model.person.Attendance; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -19,24 +21,24 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@gmail.com"), + new Payment("PAID"), new Attendance("PRESENT"), new Uid("00001"), + getTagSet("NORMAL", "NoShrimp", "GUEST")), + new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@gmail.com"), + new Payment("NOTPAID"), new Attendance("ABSENT"), new Uid("00002"), + getTagSet("VEGETARIAN", "NoNuts", "VIP")), + new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@gmail.com"), + new Payment("NOTPAID"), new Attendance("PRESENT"), new Uid("00003"), + getTagSet("VEGAN", "GUEST")), + new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@gmail.com"), + new Payment("PENDING"), new Attendance("ABSENT"), new Uid("00004"), + getTagSet("NORMAL", "NoBeef", "NoSeafood", "VIP")), + new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@gmail.com"), + new Payment("PAID"), new Attendance("PRESENT"), new Uid("00005"), + getTagSet("HALAL", "NoGluten", "GUEST")), + new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@gmail.com"), + new Payment("PENDING"), new Attendance("ABSENT"), new Uid("00006"), + getTagSet("NoBeef", "VIP")) }; } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedEvent.java b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java new file mode 100644 index 000000000000..f0e0e39cd8d5 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java @@ -0,0 +1,162 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventDate; +import seedu.address.model.event.EventName; +import seedu.address.model.event.EventStartTime; +import seedu.address.model.event.EventVenue; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Event. + */ +public class XmlAdaptedEvent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!"; + + @XmlElement(required = true) + private String name; + + @XmlElement(required = true) + private String date; + + @XmlElement(required = true) + private String venue; + + @XmlElement(required = true) + private String startTime; + + @XmlElement + private List tagged = new ArrayList<>(); + + @XmlElement + private String isNotInitialisedByUser; + + /** + * Constructs an XmlAdaptedEvent. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedEvent() {} + + /** + * Constructs an {@code XmlAdaptedEvent} with the given event details. + */ + public XmlAdaptedEvent(String name, String date, String venue, + String startTime, List tagged, String isNotInitialisedByUser) { + this.name = name; + this.date = date; + this.venue = venue; + this.startTime = startTime; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + this.isNotInitialisedByUser = isNotInitialisedByUser; + } + + /** + * Converts a given Event into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedEvent + */ + public XmlAdaptedEvent(Event source) { + name = source.getName(); + date = source.getDate(); + venue = source.getVenue(); + startTime = source.getStartTime(); + tagged = source.getEventTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + if (source.isUserInitialised() == true) { + isNotInitialisedByUser = "false"; + } else { + isNotInitialisedByUser = "true"; + } + } + + /** + * Converts this jaxb-friendly adapted event object into the model's Event object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Event toModelType() throws IllegalValueException { + final List eventTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + eventTags.add(tag.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format + (MISSING_FIELD_MESSAGE_FORMAT, EventName.class.getSimpleName())); + } + if (!EventName.isValidEventName(name)) { + throw new IllegalValueException(EventName.MESSAGE_EVENTNAME_CONSTRAINTS); + } + final EventName modelName = new EventName(name); + + if (date == null) { + throw new IllegalValueException(String.format + (MISSING_FIELD_MESSAGE_FORMAT, EventDate.class.getSimpleName())); + } + if (!EventDate.isValidEventDate(date)) { + throw new IllegalValueException(EventDate.MESSAGE_EVENTDATE_CONSTRAINTS); + } + final EventDate modelDate = new EventDate(date); + + if (venue == null) { + throw new IllegalValueException(String.format + (MISSING_FIELD_MESSAGE_FORMAT, EventVenue.class.getSimpleName())); + } + if (!EventVenue.isValidEventVenue(venue)) { + throw new IllegalValueException(EventVenue.MESSAGE_EVENTVENUE_CONSTRAINTS); + } + final EventVenue modelVenue = new EventVenue(venue); + + if (startTime == null) { + throw new IllegalValueException(String.format + (MISSING_FIELD_MESSAGE_FORMAT, EventStartTime.class.getSimpleName())); + } + if (!EventStartTime.isValidEventStartTime(startTime)) { + throw new IllegalValueException(EventStartTime.MESSAGE_EVENTSTARTTIME_CONSTRAINTS); + } + final EventStartTime modelStartTime = new EventStartTime(startTime); + + final Set modelTags = new HashSet<>(eventTags); + + boolean modelIsNotInitialisedByUser; + if (isNotInitialisedByUser == "true") { + modelIsNotInitialisedByUser = true; + } else { + modelIsNotInitialisedByUser = false; + } + + return new Event(modelName, modelDate, modelVenue, modelStartTime, modelTags, modelIsNotInitialisedByUser); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedEvent)) { + return false; + } + + XmlAdaptedEvent otherEvent = (XmlAdaptedEvent) other; + return Objects.equals(name, otherEvent.name) + && Objects.equals(date, otherEvent.date) + && Objects.equals(venue, otherEvent.venue) + && Objects.equals(startTime, otherEvent.startTime) + && tagged.equals(otherEvent.tagged); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java index c03785e5700f..8b4e0dd6a57f 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java @@ -10,11 +10,13 @@ import javax.xml.bind.annotation.XmlElement; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Attendance; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Payment; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Uid; import seedu.address.model.tag.Tag; /** @@ -31,7 +33,11 @@ public class XmlAdaptedPerson { @XmlElement(required = true) private String email; @XmlElement(required = true) - private String address; + private String payment; + @XmlElement(required = true) + private String attendance; + @XmlElement(required = true) + private String uid; @XmlElement private List tagged = new ArrayList<>(); @@ -45,11 +51,14 @@ public XmlAdaptedPerson() {} /** * Constructs an {@code XmlAdaptedPerson} with the given person details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + public XmlAdaptedPerson(String name, String phone, String email, String payment, + String attendance, String uid, List tagged) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.payment = payment; + this.attendance = attendance; + this.uid = uid; if (tagged != null) { this.tagged = new ArrayList<>(tagged); } @@ -64,7 +73,9 @@ public XmlAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + payment = source.getPayment().paymentValue; + attendance = source.getAttendance().attendanceValue; + uid = source.getUid().uidValue; tagged = source.getTags().stream() .map(XmlAdaptedTag::new) .collect(Collectors.toList()); @@ -105,16 +116,36 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + //@@author Sarah + if (payment == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Payment.class.getSimpleName())); + } + if (!Payment.isValidPayment(payment)) { + throw new IllegalValueException(Payment.MESSAGE_PAYMENT_CONSTRAINTS); + } + final Payment modelPayment = new Payment(payment); + + if (attendance == null) { + throw new IllegalValueException(String + .format(MISSING_FIELD_MESSAGE_FORMAT, Attendance.class.getSimpleName())); + } + if (!Attendance.isValidAttendance(attendance)) { + throw new IllegalValueException(Attendance.MESSAGE_ATTENDANCE_CONSTRAINTS); + } + final Attendance modelAttendance = new Attendance(attendance); + + //@@author + if (uid == null) { + throw new IllegalValueException(String + .format(MISSING_FIELD_MESSAGE_FORMAT, Uid.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + if (!Uid.isValidUid(uid)) { + throw new IllegalValueException(Uid.MESSAGE_UID_CONSTRAINTS); } - final Address modelAddress = new Address(address); + final Uid modelUid = new Uid(uid); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelName, modelPhone, modelEmail, modelPayment, modelAttendance, modelUid, modelTags); } @Override @@ -131,7 +162,8 @@ public boolean equals(Object other) { return Objects.equals(name, otherPerson.name) && Objects.equals(phone, otherPerson.phone) && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) + && Objects.equals(payment, otherPerson.payment) + && Objects.equals(attendance, otherPerson.attendance) && tagged.equals(otherPerson.tagged); } } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index b85fa4a8f07e..12ec4fe8dcff 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -10,6 +10,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -20,15 +21,21 @@ public class XmlSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_EVENT = "An event already exists."; + @XmlElement private List persons; + @XmlElement + private XmlAdaptedEvent event; + /** * Creates an empty XmlSerializableAddressBook. * This empty constructor is required for marshalling. */ public XmlSerializableAddressBook() { persons = new ArrayList<>(); + event = new XmlAdaptedEvent(); } /** @@ -37,6 +44,7 @@ public XmlSerializableAddressBook() { public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + event = new XmlAdaptedEvent(src.getEventDetails()); } /** @@ -54,6 +62,11 @@ public AddressBook toModelType() throws IllegalValueException { } addressBook.addPerson(person); } + Event event = this.event.toModelType(); + if (addressBook.hasEvent()) { + throw new IllegalValueException(MESSAGE_DUPLICATE_EVENT); + } + addressBook.setEvent(event); return addressBook; } @@ -66,6 +79,7 @@ public boolean equals(Object other) { if (!(other instanceof XmlSerializableAddressBook)) { return false; } - return persons.equals(((XmlSerializableAddressBook) other).persons); + return persons.equals(((XmlSerializableAddressBook) other).persons) + && event.equals(((XmlSerializableAddressBook) other).event); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index b43de90a2b9f..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); - } -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 3d7aaded5640..eb931ecaa22f 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -107,14 +107,14 @@ private void handleCommandEntered() { // process result of the command commandTextField.setText(""); logger.info("Result: " + commandResult.feedbackToUser); - raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); + raise(new NewResultAvailableEvent(commandResult.feedbackToUser, true)); } catch (CommandException | ParseException e) { initHistory(); // handle command failure setStyleToIndicateCommandFailure(); logger.info("Invalid command: " + commandTextField.getText()); - raise(new NewResultAvailableEvent(e.getMessage())); + raise(new NewResultAvailableEvent(e.getMessage(), false)); } } diff --git a/src/main/java/seedu/address/ui/EmailWindow.java b/src/main/java/seedu/address/ui/EmailWindow.java new file mode 100644 index 000000000000..9a6533c66536 --- /dev/null +++ b/src/main/java/seedu/address/ui/EmailWindow.java @@ -0,0 +1,151 @@ +package seedu.address.ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.Email; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +//@@author aaryamNUS +/** + * Controller for an email window. + */ +public class EmailWindow extends UiPart { + + private static final Logger logger = LogsCenter.getLogger(EmailWindow.class); + private static final String FXML = "EmailWindow.fxml"; + private String[] information = new String[4]; + private boolean isStillOpen = false; + private boolean isSendButtonPressed = false; + private boolean isQuitButtonPressed = false; + private Email email = new Email() { + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + return null; + } + }; + + @FXML + private VBox emailContainer; + + @FXML + private TextField emailField; + + @FXML + private TextArea messageField; + + @FXML + private PasswordField passwordField; + + @FXML + private Button quitButton; + + @FXML + private Button sendButton; + + @FXML + private TextField subjectField; + + public EmailWindow() { + super(FXML, new Stage()); + } + + /** + * This is the event handler when the Send Email Button is pressed. + * Checks whether username, password, email subject and email message are + * provided by the user. If any of the parameters are either null or an + * empty string, the respective alert is thrown. Moreover, if all the fields are + * correct, the information String array is returned back to the Email abstract class + * to construct the email message and connect to the Gmail smtp server + */ + @FXML + public void handleSendEmailButtonAction() { + information[0] = emailField.getText(); + information[1] = passwordField.getText(); + information[2] = subjectField.getText(); + information[3] = messageField.getText(); + + if (emailField.getText() == null + || emailField.getText().replaceAll("\\s+", "").equals("")) { + Alert alert = new Alert(Alert.AlertType.ERROR, Messages.MESSAGE_USERNAME_NOT_PROVIDED, ButtonType.OK); + alert.showAndWait(); + isStillOpen = true; + } else if (passwordField.getText() == null + || passwordField.getText().replaceAll("\\s+", "").equals("")) { + Alert alert = new Alert(Alert.AlertType.ERROR, Messages.MESSAGE_PASSWORD_NOT_PROVIDED, ButtonType.OK); + alert.showAndWait(); + + isStillOpen = true; + } else if (subjectField.getText() == null + || subjectField.getText().replaceAll("\\s+", "").equals("")) { + Alert alert = new Alert(Alert.AlertType.ERROR, Messages.MESSAGE_EMAIL_SUBJECT_NOT_PROVIDED, ButtonType.OK); + alert.showAndWait(); + + isStillOpen = true; + } else if (messageField.getText() == null + || messageField.getText().replaceAll("\\s+", "").equals("")) { + Alert alert = new Alert(Alert.AlertType.ERROR, Messages.MESSAGE_EMAIL_MESSAGE_NOT_PROVIDED, ButtonType.OK); + alert.showAndWait(); + + isStillOpen = true; + } else { + logger.log(Level.INFO, "All fields from EmailWindow received successfully!"); + isStillOpen = false; + isSendButtonPressed = true; + } + + if (!isStillOpen) { + getRoot().close(); + } + } + + /** + * This is the event handler when the Quit Button is pressed. When done so, + * the EmailWindow event closes and the command box provides a simple message to the user + * indicating that they have not sent an email to any guests + */ + @FXML + public void handleQuitButtonAction() { + isQuitButtonPressed = true; + getRoot().close(); + } + + /** + * Shows the email window + */ + public void showAndWait() { + logger.fine("Showing email window"); + isStillOpen = true; + try { + getRoot().showAndWait(); + } catch (AssertionError | Exception e) { + System.out.println("Unhandled NSEvent - usually caused by Mac OS"); + } + } + + public String[] getInformation() { + return information; + } + + public boolean isSendButton() { + return isSendButtonPressed; + } + + public boolean isQuitButton() { + return isQuitButtonPressed; + } +} diff --git a/src/main/java/seedu/address/ui/EventDetailsPanel.java b/src/main/java/seedu/address/ui/EventDetailsPanel.java new file mode 100644 index 000000000000..4ce4a57b75c1 --- /dev/null +++ b/src/main/java/seedu/address/ui/EventDetailsPanel.java @@ -0,0 +1,91 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.event.Event; + +/** + * The Browser Panel of the App. + */ +public class EventDetailsPanel extends UiPart { + + private static final String FXML = "EventDetailsPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private seedu.address.model.event.Event event; + + @FXML + private HBox eventDetailsPanel; + @FXML + private Label name; + @FXML + private Label date; + @FXML + private Label venue; + @FXML + private Label startTime; + + @FXML + private FlowPane tags; + + public EventDetailsPanel(seedu.address.model.event.Event event) { + super(FXML); + fillInEventDetails(event); + registerAsAnEventHandler(this); + } + + /** + * Fills in details of the selected {@code person} to the PersonDisplay Ui component + */ + private void fillInEventDetails(Event event) { + if (event.isUserInitialised()) { + name.setText(event.getName()); + date.setText(event.getDate()); + venue.setText(event.getVenue()); + startTime.setText(event.getStartTime()); + removeTags(); + createTags(event); + } else { + name.setText("Please put in event details"); + date.setText(""); + venue.setText(""); + startTime.setText(""); + removeTags(); + } + } + + /** + * Method createTags initialises the tag labels for {@code event} + * This code was adapted from @aaryamNUS's implementation + */ + private void createTags(seedu.address.model.event.Event event) { + event.getEventTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColor("cyan")); + tags.getChildren().add(tagLabel); + }); + } + + /** + * Removes all tags from the Ui component + */ + private void removeTags() { + tags.getChildren().clear(); + } + + @Subscribe + private void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + fillInEventDetails(event.getNewDetails()); + } +} diff --git a/src/main/java/seedu/address/ui/ImportReportWindow.java b/src/main/java/seedu/address/ui/ImportReportWindow.java new file mode 100644 index 000000000000..102c28372e63 --- /dev/null +++ b/src/main/java/seedu/address/ui/ImportReportWindow.java @@ -0,0 +1,59 @@ +package seedu.address.ui; + +import java.util.List; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.converters.error.ImportError; + +/** + * Controller for a import report window. + */ +public class ImportReportWindow extends UiPart { + + private static final Logger logger = LogsCenter.getLogger(ImportReportWindow.class); + private static final String FXML = "ImportReportWindow.fxml"; + private List errors; + + @FXML + private TableView importErrorTable; + + @FXML + private TableColumn inputDataColumn; + + @FXML + private TableColumn errorMessageColumn; + + public ImportReportWindow(List errors) { + super(FXML, new Stage()); + this.errors = errors; + setConnections(); + } + + @FXML + private void handleCloseImportReportWindow() { + getRoot().close(); + logger.fine("Import report closed"); + + } + + /** + * Shows the import report window + */ + public void show() { + logger.fine("Showing import report"); + getRoot().show(); + } + + private void setConnections() { + errorMessageColumn.setCellValueFactory(importError -> importError.getValue().errorMessageProperty()); + inputDataColumn.setCellValueFactory(importError -> importError.getValue().dataInputProperty()); + importErrorTable.setItems(FXCollections.observableArrayList(errors)); + } +} + diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..56112c70b03f 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,5 +1,6 @@ package seedu.address.ui; +import java.util.List; import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; @@ -17,7 +18,9 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.events.ui.ShowHelpRequestEvent; +import seedu.address.commons.events.ui.ShowImportReportEvent; import seedu.address.logic.Logic; +import seedu.address.logic.converters.error.ImportError; import seedu.address.model.UserPrefs; /** @@ -34,7 +37,6 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; @@ -58,6 +60,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane personDisplayPlaceholder; + public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { super(FXML, primaryStage); @@ -87,6 +92,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -119,8 +125,8 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + EventDetailsPanel eventDetailsPanel = new EventDetailsPanel(logic.getEventDetails()); + browserPlaceholder.getChildren().add(eventDetailsPanel.getRoot()); personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); @@ -128,7 +134,11 @@ void fillInnerParts() { ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + PersonDisplay personDisplay = new PersonDisplay(); + personDisplayPlaceholder.getChildren().add(personDisplay.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath(), + logic.getFilteredPersonList().size(), logic.getEventDetails()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -191,8 +201,14 @@ public PersonListPanel getPersonListPanel() { return personListPanel; } - void releaseResources() { - browserPanel.freeResources(); + /** + * Creates and shows the ImportReportWindow + */ + private void showImportReport(List errors) { + if (errors != null && !errors.isEmpty()) { + ImportReportWindow importReportWindow = new ImportReportWindow(errors); + importReportWindow.show(); + } } @Subscribe @@ -200,4 +216,10 @@ private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + @Subscribe + private void handleShowImportReportEvent(ShowImportReportEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + showImportReport(event.errors); + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..5a10567c276d 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -13,7 +13,6 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; - /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. * As a consequence, UI elements' variable names cannot be set to such keywords @@ -33,10 +32,14 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private Label attendance; @FXML private Label email; @FXML + private Label payment; + @FXML + private Label uid; + @FXML private FlowPane tags; public PersonCard(Person person, int displayedIndex) { @@ -45,10 +48,26 @@ public PersonCard(Person person, int displayedIndex) { id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); + attendance.setText(person.getAttendance().attendanceValue); email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + payment.setText(person.getPayment().paymentValue); + uid.setText(person.getUid().uidValue); + createTags(person); + } + + //@@author aaryamNUS + /** + * Method createTags initialises the tag labels for {@code person} + * Note: This code was adapted from the example implementation provide by @yamgent from SE-EDU + */ + private void createTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); } + //@@author @Override public boolean equals(Object other) { diff --git a/src/main/java/seedu/address/ui/PersonDisplay.java b/src/main/java/seedu/address/ui/PersonDisplay.java new file mode 100644 index 000000000000..c5c6d8d76de7 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonDisplay.java @@ -0,0 +1,112 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.PersonPanelSelectionClearedEvent; +import seedu.address.model.person.Person; + +//@@author wm28 +/** + * An UI component that displays information of a selected {@code Person} on the MainWindow. + */ +public class PersonDisplay extends UiPart { + private static final String FXML = "PersonDisplay.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label attendance; + @FXML + private Label email; + @FXML + private Label payment; + @FXML + private Label uid; + @FXML + private FlowPane tags; + + public PersonDisplay() { + super(FXML); + registerAsAnEventHandler(this); + } + //@@author + //@@author aaryamNUS + /** + * Method createTags initialises the tag labels for {@code person} + * Note: This code was adapted from the example implementation provide by @yamgent from SE-EDU + */ + private void createTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } + + //@@author wm28 + /** + * Removes all tags from the PersonDisplay Ui component + */ + private void removeTags() { + tags.getChildren().clear(); + } + + /** + * Fills in details of the selected {@code person} to the PersonDisplay Ui component + */ + private void fillInPersonDetails(Person person) { + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + attendance.setText(person.getAttendance().attendanceValue); + email.setText(person.getEmail().value); + payment.setText(person.getPayment().paymentValue); + uid.setText(person.getUid().uidValue); + removeTags(); + createTags(person); + logger.info("Filled in PersonDisplay: " + person.toString()); + } + + /** + * Removes details of the previously selected {@code Person} displayed in the PersonDisplay Ui component + */ + private void removePersonDetails() { + name.setText(""); + phone.setText(""); + attendance.setText(""); + email.setText(""); + payment.setText(""); + uid.setText(""); + removeTags(); + logger.info("Cleared fields in PersonDisplay "); + } + + @Subscribe + private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + "Person selection changed, displaying newly selected guest details")); + fillInPersonDetails(event.getNewSelection()); + } + + /** + * Clears {@code Person} details in PersonDisplay Ui component when selection is cleared in the PersonListPanel + */ + @Subscribe + public void handlePersonPanelSelectionClearedEvent(PersonPanelSelectionClearedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + "Local data changed, clearing selected guest details ")); + removePersonDetails(); + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index 80080adb4305..47805bdc5d91 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -11,8 +11,10 @@ import javafx.scene.control.ListView; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.model.AddressBookChangedEvent; import seedu.address.commons.events.ui.JumpToListRequestEvent; import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.PersonPanelSelectionClearedEvent; import seedu.address.model.person.Person; /** @@ -80,4 +82,11 @@ protected void updateItem(Person person, boolean empty) { } } + @Subscribe + public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + "Local data changed, clearing guest list selection")); + personListView.getSelectionModel().clearSelection(); + raise(new PersonPanelSelectionClearedEvent()); + } } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java index d05536bbee96..819ea04682c0 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultDisplay.java @@ -7,6 +7,7 @@ import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.layout.Region; @@ -18,6 +19,7 @@ */ public class ResultDisplay extends UiPart { + public static final String ERROR_IN_COMMAND = "error"; private static final Logger logger = LogsCenter.getLogger(ResultDisplay.class); private static final String FXML = "ResultDisplay.fxml"; @@ -35,7 +37,39 @@ public ResultDisplay() { @Subscribe private void handleNewResultAvailableEvent(NewResultAvailableEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - Platform.runLater(() -> displayed.setValue(event.message)); + Platform.runLater(() -> { + displayed.setValue(event.message); + + if (event.isCorrectCommand) { + setStyleforSuccessfulCommand(); + } else { + setStyleforIncorrectCommand(); + } + }); + } + + /** + * Sets the {@code ResultDisplay} style to display the default style + * Note: This code was adapted from the example implementation provide by @yamgent from SE-EDU + */ + + private void setStyleforSuccessfulCommand() { + resultDisplay.getStyleClass().remove(ERROR_IN_COMMAND); + } + + /** + * Sets the {@code ResultDisplay} style to display an incorrect command + * Note: This code was adapted from the example implementation provide by @yamgent from SE-EDU + */ + + private void setStyleforIncorrectCommand() { + ObservableList styleClass = resultDisplay.getStyleClass(); + + if (styleClass.contains(ERROR_IN_COMMAND)) { + return; + } + + styleClass.add(ERROR_IN_COMMAND); } } diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..7a62846114ae 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -21,8 +21,10 @@ */ public class StatusBarFooter extends UiPart { + public static final String DAYS_LEFT_STATUS = "Number of days left to your event: %s"; public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; + public static final String TOTAL_PERSONS_STATUS = "%d guest(s) total"; /** * Used to generate time stamps. @@ -41,13 +43,19 @@ public class StatusBarFooter extends UiPart { @FXML private StatusBar syncStatus; @FXML + private StatusBar totalPersonsStatus; + @FXML private StatusBar saveLocationStatus; + @FXML + private StatusBar daysLeft; - public StatusBarFooter(Path saveLocation) { + public StatusBarFooter(Path saveLocation, int totalPersons, seedu.address.model.event.Event event) { super(FXML); setSyncStatus(SYNC_STATUS_INITIAL); setSaveLocation(Paths.get(".").resolve(saveLocation).toString()); + setTotalPersons(totalPersons); + setDaysLeft(event); registerAsAnEventHandler(this); } @@ -73,11 +81,25 @@ private void setSyncStatus(String status) { Platform.runLater(() -> syncStatus.setText(status)); } + private void setTotalPersons(int totalPersons) { + Platform.runLater(() -> totalPersonsStatus.setText(String.format(TOTAL_PERSONS_STATUS, totalPersons))); + } + + private void setDaysLeft(seedu.address.model.event.Event event) { + if (event.isUserInitialised()) { + Platform.runLater(() -> daysLeft.setText(String.format(DAYS_LEFT_STATUS, event.getDaysLeft()))); + } else { + Platform.runLater(() -> daysLeft.setText(String.format(DAYS_LEFT_STATUS, "NO DETAILS"))); + } + + } @Subscribe public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); + setTotalPersons(abce.data.getPersonList().size()); + setDaysLeft(abce.data.getEventDetails()); } } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..995de5a653ed 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/docs/images/InvitésLogoSmall.png"; private Logic logic; private Config config; @@ -66,7 +66,7 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); + //mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index 5c237e57154b..b69bb9ee579c 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -19,6 +19,19 @@ public abstract class UiPart { /** Resource folder where FXML files are stored. */ public static final String FXML_FILE_FOLDER = "/view/"; + /** + * The following string array represents different tag colours associated with each guest in the list. + * Each colour represents a charecteristic of the guest, as summarised below: + * Orange - Absent, Yellow - Present, Light Blue - VIP, + * White - Guest Speaker, Black - Not Paid, Purple - Paid, + * - Event tags + * Default - a tag that is not supported by the application specifications + */ + private static final String[] TAG_COLORS = {"orange", "yellow", "lightblue", + "white", "bronze", "silver", + "gold", "platinum", "veg", + "halal", "cyan", "default"}; + private final FXMLLoader fxmlLoader = new FXMLLoader(); /** @@ -103,4 +116,41 @@ private static URL getFxmlFileUrl(String fxmlFileName) { return requireNonNull(fxmlFileUrl); } + //@@author aaryamNUS + /** + Method getTagColor returns the specific color style for {@code tagName}'s label. + */ + public String getTagColor(String tagName) { + /** + * Using a switch statement with the tag name ensures the color of the tag remains consistent + * during different iterations of the code + */ + switch (tagName.replaceAll("\\s+", "").toLowerCase()) { + case "absent": + return TAG_COLORS[0]; + case "present": + return TAG_COLORS[1]; + case "vip": + return TAG_COLORS[2]; + case "guestspeaker": + case "guest": + return TAG_COLORS[3]; + case "bronze": + return TAG_COLORS[4]; + case "silver": + return TAG_COLORS[5]; + case "gold": + return TAG_COLORS[6]; + case "platinum": + return TAG_COLORS[7]; + case "veg": + return TAG_COLORS[8]; + case "halal": + return TAG_COLORS[9]; + case "cyan": + return TAG_COLORS[10]; + default: + return TAG_COLORS[11]; + } + } } diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..2f5bca40d4c4 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -27,6 +27,14 @@ .text-field { -fx-font-size: 12pt; -fx-font-family: "Segoe UI Semibold"; + -fx-prompt-text-fill: silver; +} + +.text-area { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-prompt-text-fill: silver; + -fx-background-color: darkgrey; } .tab-pane { @@ -65,7 +73,7 @@ } .table-view .column-header .label { - -fx-font-size: 20pt; + -fx-font-size: 15pt; -fx-font-family: "Segoe UI Light"; -fx-text-fill: white; -fx-alignment: center-left; @@ -123,13 +131,13 @@ .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: #FFFFFF; } .cell_small_label { -fx-font-family: "Segoe UI"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #FFFFFF; } .stack-pane { @@ -341,11 +349,127 @@ -fx-vgap: 3; } +/*@@author aaryamNUS +* Adding the definitions for the tag labels +*/ #tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; -fx-font-size: 11; } + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ff9900; +} + +#tags .yellow { + -fx-text-fill: white; + -fx-background-color: #cb9d1d; +} + +#tags .lightblue { + -fx-text-fill: black; + -fx-background-color: #96e1f0; +} + +#tags .white { + -fx-text-fill: black; + -fx-background-color: #ffffff; +} + +#tags .bronze { + -fx-text-fill: black; + -fx-background-color: #cd7f32; +} + +#tags .silver { + -fx-text-fill: black; + -fx-background-color: #C0C0C0; +} + +#tags .gold { + -fx-text-fill: black; + -fx-background-color: #DAA520; +} + +#tags .platinum { + -fx-text-fill: black; + -fx-background-color: #E5E4E2; +} + +#tags .veg { + -fx-text-fill: white; + -fx-background-color: #228B22; +} + +#tags .halal { + -fx-text-fill: black; + -fx-background-color: #ADFF2F; +} + +#tags .cyan { + -fx-text-fill: black; + -fx-background-color: #00FFFF; +} + +#tags .default { + -fx-text-fill: red; + -fx-background-color: #ffffff; +} + +#tags .transparent { + -fx-text-fill: red; + -fx-background-color: null !important; +} +/* @@author */ + +#importReportTitle { + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 25pt; + -fx-text-fill: white; +} + +#emailContainer { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + +#emailLabel { + -fx-text-fill: white; +} + +#passwordLabel { + -fx-text-fill: white; +} + +#messageLabel { + -fx-text-fill: white; +} + +#subjectLabel { + -fx-text-fill: white; +} + +#emailField { + -fx-text-fill: black; + -fx-background-color: lightgrey; +} + +#passwordField { + -fx-text-fill: black; + -fx-background-color: lightgrey; +} + +#messageField { + -fx-text-fill: black; + -fx-background-color: lightgrey; +} + +#subjectField { + -fx-text-fill: black; + -fx-background-color: lightgrey; +} + + diff --git a/src/main/resources/view/EmailWindow.fxml b/src/main/resources/view/EmailWindow.fxml new file mode 100644 index 000000000000..0a0b315982e1 --- /dev/null +++ b/src/main/resources/view/EmailWindow.fxml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + +