diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000000..0cd51c5fb97
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,25 @@
+name: MarkBind Action
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install Graphviz
+ run: sudo apt-get install graphviz
+ - name: Install Java
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Build & Deploy MarkBind site
+ uses: MarkBind/markbind-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ rootDirectory: './docs'
+ baseUrl: '/tp' # assuming your repo name is tp
+ version: '^5.2.0'
diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..eab4c7db6a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+docs/_markbind/logs/
diff --git a/README.md b/README.md
index 13f5c77403f..2ae2da57683 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,16 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S2-CS2103T-W10-4/tp/actions)
![Ui](docs/images/Ui.png)
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
+# TutorsContactsPro
+
+* TutorsContactsPro is a app for computer science tutor managing students for tutorials (all cs course or type of teaching)
+* TutorsContactsPro allows tutors to have faster and more convenient access to their list of students (from different classes), optimized for those who prefer a clean UI with the use of CLI.
+
* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
* It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
* It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
+* It is named `TutorsContactsPro` (`TCP` for short) which means it is a pro-version app for tutors to manage their students
+* For the detailed documentation of this project, see the **[TutorsContactsPro Product Website](https://ay2324s2-cs2103t-w10-4.github.io/tp/index.html)**.
* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/build.gradle b/build.gradle
index a2951cc709e..926c4744064 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,12 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = '[CS2103T-W10-4][TutorsContactsPro].jar'
}
+run {
+ enableAssertions = true
+}
+
+
defaultTasks 'clean', 'test'
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000000..1748e487fbd
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,23 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+_markbind/logs/
+
+# Dependency directories
+node_modules/
+
+# Production build files (change if you output the build to a different directory)
+_site/
+
+# Env
+.env
+.env.local
+
+# IDE configs
+.vscode/
+.idea/*
+*.iml
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..888747031fc 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,59 +1,54 @@
---
-layout: page
-title: About Us
+ layout: default.md
+ title: "About Us"
---
+# About Us
+
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
-
-
-
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+### Doye Lee
-* Role: Project Advisor
+
-### Jane Doe
+[[github](https://github.com/doyelee0313)]
+[[portfolio](team/doyelee0313.md)]
-
-
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
-
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
+* Responsibilities: Development + UI + Documentation
-### Johnny Doe
+### Soh Kia Hwee Jocelyn
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](https://github.com/jocelyn-soh)]
+[[portfolio](team/jocelyn-soh.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Dev Ops + Development + Code Quality
-### Jean Doe
+### Stella Lye Si Yu
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[homepage](http://STELLA-LYE.github.io)]
+[[github](https://github.com/STELLA-LYE)]
+[[portfolio](team/stella-lye.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Dev Ops + Development + UI
-### James Doe
+### Toh Li Heng
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[homepage](http://tohlh.github.io)]
+[[github](http://github.com/tohlh)]
+[[portfolio](team/tohlh.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Dev Ops + Development
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 13cf0faea16..32f6255f3b9 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -1,6 +1,8 @@
---
-layout: page
-title: Configuration guide
+ layout: default.md
+ title: "Configuration guide"
---
+# Configuration guide
+
Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`).
diff --git a/docs/DevOps.md b/docs/DevOps.md
index d2fd91a6001..3f952783199 100644
--- a/docs/DevOps.md
+++ b/docs/DevOps.md
@@ -1,12 +1,15 @@
---
-layout: page
-title: DevOps guide
+ layout: default.md
+ title: "DevOps guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# DevOps guide
---------------------------------------------------------------------------------------------------------------------
+
+
+
+
## Build automation
@@ -73,7 +76,7 @@ Any warnings or errors will be printed out to the console.
Here are the steps to create a new release.
-1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java).
+1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/MainApp.java).
1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`).
1. Tag the repo with the version number. e.g. `v0.1`
1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created.
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 1b56bb5d31b..20cf2919c98 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,15 +1,19 @@
---
-layout: page
-title: Developer Guide
+ layout: default.md
+ title: "Developer Guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+
+# TutorsContactsPro Developer Guide
+
+
+
--------------------------------------------------------------------------------------------------------------------
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+_{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }_
--------------------------------------------------------------------------------------------------------------------
@@ -19,16 +23,13 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
--------------------------------------------------------------------------------------------------------------------
-## **Design**
-
-
+
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
-
+## **Design**
### Architecture
-
+
The ***Architecture Diagram*** given above explains the high-level design of the App.
@@ -36,7 +37,7 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
+**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/MainApp.java)) is in charge of the app launch and shut down.
* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
@@ -49,11 +50,13 @@ The bulk of the app's work is done by the following four components:
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
+
+
**How the architecture components interact with each other**
The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
-
+
Each of the four main components (also shown in the diagram above),
@@ -62,19 +65,19 @@ Each of the four main components (also shown in the diagram above),
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
-
+
The sections below give more details of each component.
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/ui/Ui.java)
-![Structure of the UI Component](images/UiClassDiagram.png)
+
The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
@@ -83,20 +86,26 @@ The `UI` component,
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+
+
### Logic component
-**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
-
+
The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example.
-![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
+
+
+
-
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
-
+**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
+
+
+
How the `Logic` component works:
@@ -108,40 +117,44 @@ How the `Logic` component works:
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
-
+
How the parsing works:
* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
-### Model component
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+
-
+### Model component
+**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/model/Model.java)
+
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
+* stores the major book data i.e., all `Person` and `Group` objects (which are contained in a `UniquePersonList` and `UniqueGroupList` object, respectively).
* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+
+
+**Note:** An alternative (arguably, a more OOP) model is given below. It has a `Group` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Group` object per unique group, instead of each `Person` needing their own `Group` objects.
-
+
-
+
+
### Storage component
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/major/storage/Storage.java)
-
+
The `Storage` component,
-* can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
+* can save both major book data and user preference data in JSON format, and read them back into corresponding objects.
* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
@@ -151,84 +164,600 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
--------------------------------------------------------------------------------------------------------------------
+
+
## **Implementation**
This section describes some noteworthy details on how certain features are implemented.
+
+## Delete a student
+
+### About
+
+The delete student feature allows users to delete an existing student information from the student contact list
+using the command `delete INDEX`.
+
+### How it is implemented
+
+The `delete` command mechanism is facilitated by the `DeleteCommand` and the `DeleteCommandParser`.
+It allows users to delete a student contact from the student contact list.
+It uses the `AddressBook#removePerson(Person key)` which is exposed in the `Model`
+interface as `Model#deletePerson(Person personToDelete)`. Then, the `remove(Person person)` is called on the `UniquePersonList`
+in `TutorsContactsPro` to delete the student contact from the list.
+
+A modification from AB3 delete mechanism is that the `delete` command also involves the facilitation of the `AddressBook#deassignPerson(Person persontToDeassign, Group group)`
+which is exposed in the `Model` interface as `Model#deassignPerson(Person person, Group group)`, which result in the call of `Group#deassign(Person person)` to
+deassign the deleted student contact from all previously assigned groups.
+
+#### Parsing input
+
+1. The users inputs the `delete` command.
+
+2. The `TutorsContactsPro` then preliminary process the input and creates a new `DeleteCommandParser`.
+
+3. The `DeleteCommandParser` then calls the `ParserUtil#parseIndex()` to check for the validity of the `INDEX`. At this stage, if the `INDEX is invalid or absent`, `ParseException` would be thrown.
+
+4. The `DeleteCommandParser` then creates the `DeleteCommand` based on the processed input.
+
+
+#### Command execution
+
+5. The `LogicManager` executes the `DeleteCommand`.
+
+6. The `DeleteCommand` calls the `Model#getFilteredPersonList()` to get the unmodifiable view of the filtered student list to get the target
+ student to delete based on the provided `INDEX`.
At this stage, `CommandException` would be thrown if the input `INDEX`
+ is invalid (i.e. `INDEX` exceeds the size of the student contact list).
+
+7. The `DeleteCommand` then calls the `Model#deletePerson(Person personToDelete)` to delete the target student contact from the
+ student contact list.
+
+
+
+#### Displaying of result
+
+8. Finally, the `DeleteCommand` creates a `CommandResult` with a success message and return it to the `LogicManager` to complete the command execution.
+ The GUI would also be updated on this change in the student contact list accordingly.
+
+The following sequence diagram shows how the `delete` mechanism works:
+
+
+
+
+
+The following activity diagram summarizes what happens when a user executes the `delete` command:
+
+
+
+## Find Students by Name
+
+### About
+
+The `find` command allows the search of a student using name via the command `find KEYWORDS` where `KEYWORDS` is the `NAME` of the student.
+
+### How it is implemented
+The feature is made possible through introducing an `AttributeContainsKeywordsPredicate` interface which is
+implemented by all the `Person`'s attributes. Hence, there are more classes being introduced -
+`NameContainsKeywordsPredicate`, `PhoneContainsKeywordsPredicate` and so on.
+
+Given below is the example usage scenario and how the `find` mechanism behaves at each step.
+
+#### Parsing user input
+
+1. The user inputs the `find` command and provide the input with the name of the student in which the user wants to find the contact.
+
+2. The `TutorsContactsPro` then does preliminary processing to the user input and creates a new `FindCommandParser`.
+
+3. The `FindCommandParser` then parses the user input and checks whether the required `KEYWORD` is present.
+
+4. If the required `KEYWORD` is present, `FindCommandParser` will then call the `ParserUtil#parseName()`
+ to check for the validity of the input `NAME`.
+ At this stage, `ParseException` would be thrown if the
+ `NAME` specified is invalid.
+
+5. The `FindCommandParser` then creates the `FindCommand` based on the processed inputs.
+
+#### Command execution
+
+6. The `LogicManager` executes the `FindCommand`.
+7. The `FindCommand` calls the `Model#updateFilteredPersonList()` to update the filtered person list based on the user input `KEYWORD`.
+8. The `FindCommand` then calls the `Model#getFilteredPersonList()#size()` to get the size of the person list. The size will correspond to the number of persons listed.
+
+
+
+#### Displaying of result
+9. Finally, the `FindCommand` creates a `CommandResult` with a success message and return it to the `LogicManager` to complete the command execution.
+ The GUI would also be updated on this change in the student list and update the display of student list accordingly.
+
+The following sequence diagram shows how the `find` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `find` command:
+
+
+
+
+
+### Design considerations
+
+#### Aspect: How the find command finds the student
+
+* **Alternative 1 (current choice)**: finds student contact without any prefixes.
+ * Pros: Simplifies the process for users to find student contacts without needing to recall any specific prefixes. This makes the command easy to remember.
+ * Cons: In some cases, the prefix can serve as a reminder on what student attribute is used for `find` command to search for students. User may be left confused and find difficult to remember that find search for students using their name without the `n/` prefix.
+
+* **Alternative 2 (current choice)**: finds through partial search (eg. `Dav` to find `David`)
+ * Pros: Enables users to save time by not requiring complete details for the search.
+ * Cons: When there are multiple contacts with similar names (e.g., Ben, Benedict, Benny), a partial search may not yield significant benefits, as users would still need to input almost the full name to locate the desired person.
+
+
+## Filter Students by Group
+
+#### About
+The filter students by group feature allows users to filter and list students in the address book whose group name matches the specified keywords.
+It helps users to quickly find students belonging to specific groups.
+
+#### How it is implemented
+The filtering mechanism is facilitated by the `FilterCommand` and the `FilterCommandParser`.
+It enables users to filter students based on the groups they belong to. It utilizes the `Model` interface to interact with the student contact list.
+
+#### Parsing input
+
+1. The users inputs the `filter` command along with the group keywords.
+
+2. The TutorsContactsPro then preliminarily processes the input and creates a new `FilterCommandParser`.
+
+3. The `FilterCommandParser` then checks for the validity of the provided group keywords. If the keywords are invalid or absent, a `ParseException` is thrown.
+
+4. The `FilterCommandParser` creates the `FilterCommand` based on the processed input, with a `GroupContainsKeywordsPredicate` to filter students based on the specified groups.
+
+#### Command execution
+
+5. The `LogicManager` executes the `FilterCommand`.
+
+6. The `FilterCommand` calls the `Model#updateFilteredPersonList()` method to update the filtered person list according to the specified group keywords.
+
+
+
+#### Displaying results
+
+7. Finally, the `FilterCommand` creates a CommandResult with the list of students matching the specified groups, and returns it to the LogicManager to complete the command execution.
+The GUI would also be updated accordingly to display the filtered list of students.
+
+The following sequence diagram illustrates how the `filter` mechanism works:
+
+
+The following activity diagram summarizes what happens when a user executes the `filter` command:
+
+
+
+### Design considerations
+
+#### Aspect: How the filter command filters student
+
+* **Alternative 1 (current choice)**: filters student contact without any prefixes.
+ * Pros: Simplifies the process for users to find student contacts without needing to recall any specific prefixes. This makes the command easy to remember.
+ * Cons: In some cases, the prefix can serve as a reminder on what student attribute is used for `filter` command to search for students. User may confuse this command with the previous `find` command.
+
+* **Alternative 2**: filter only allows for a single `GROUP_NAME` input
+ * Pros: Enables users to filter students via a single group so that it remains more organised.
+ * Cons: There may be cases where a student belongs to more than one group that is taught by the user. This will result in inconvenience when the user wants to retrieve student data from multiple groups.
+ E.g. `John` belongs to both `TUT04`and `LAB05` that is taught by the user. Filter student contacts separately for the two groups will result in `John` contact to appear twice which can be inconvenient when the user wants to retrieve unique student contacts from the two groups.
+
+* **Alternative 3 (current choice)**: filter only allows for multiple `GROUP_NAME` input
+ * Pros: Enables users to obtain unique student contact list from multiple groups.
+ * Cons: It may not be commonly used as users are more likely to filter via a single group since the `Results` tab wil already display all student contacts.
+
+
+## Mark Student's Attendance
+
+#### About
+
+Marks the student's attendance according to the group name and week number. This allows users to easily keep track of students' attendance details.
+
+#### How it is implemented
+The mark attendance mechanism is facilitated by the `MarkAttendanceCommand` and the `MarkAttendanceCommandParser`.
+It utilizes the `Model` interface to interact with the student contact list.
+
+#### Parsing input
+
+1. The users inputs the `mark` command along with the desired student index, group name, week number and the student's attendance (whether they are present, `P` or absent `A`)
+
+2. The TutorsContactsPro then preliminarily processes the inputs and creates a new `MarkAttendanceCommandParser`.
+
+3. The `MarkAttendanceCommandParser` then parses the user input and checks whether all the input attributes are present by checking the presence of the prefixes for group, week and attendance which are `/g`, `/w` and `/a` respectively.
+ It also checks whether the command is in the correct format.
At this stage, if not all the prefixes are present,
+ `ParseException` would be thrown.
+
+#### Command execution
+
+5. The `LogicManager` executes the `MarkAttendanceCommand`.
+
+6. The `MarkAttendanceCommand` calls the `Model#getFilteredPersonList()` to get the unmodifiable view of the filtered student list to get the target
+ student to mark attendance based on the provided `INDEX`.
At this stage, `CommandException` would be thrown if the input `INDEX`
+ is invalid (i.e. `INDEX` exceeds the size of the student contact list).
+
+#### Displaying results
+
+7. Finally, the `MarkAttendanceCommand` it returns a CommandResult object indicating the success or failure of the operation.
+The result message is then displayed to the user via the GUI. The attendance table of the particular studne will then be updated accordingly.
+
+The following sequence diagram illustrates how the `mark` mechanism works:
+
+
+
+
+The following activity diagram summarizes what happens when a user executes the `mark` command:
+
+
+
+
+
+## Add group feature
+
+### About
+
+The add group feature allows users users to create a new group in TutorsContactsPro.
+`addgroup g/GROUP_NAME`.
+
+### How it is implemented
+
+The `addgroup` command mechanism is facilitated by the `AddGroupCommand` and the `AddGroupCommandParser`.
+It allows TA users to add a not-already-existing-group to TutorsContactsPro.
+It uses the `AddressBook#addGroup(Group group)` which is exposed in the `Model`
+interface as `Model#addGroup(Group group)`. Then, the `add(Group groupToAdd)` is called on the `UniqueGroupList`
+in `AddressBook` to add the group to the group list.
+
+Given below is the example usage scenario and how the add group mechanism behaves at each step.
+
+#### Parsing user input
+
+1. The users user inputs the `addgroup` command and provides the `GROUP_NAME` of the group in which the TA user wants to add.
+
+2. The `TutorsContactsPro` then does preliminary processing to the input and creates a new `AddGroupCommandParser`.
+
+3. The `AddGroupCommandParser` then parses the user input and checks whether all the input attributes are present by checking the presence of the prefixes.
+ It also checks whether the command is in the correct format. In this case, the required prefix and attribute is `g/GROUP_NAME`.
At this stage, if not all the prefixes are present,
+ `ParseException` would be thrown.
+
+4. If the required prefixes and attributes are present (i.e. `g/GROUP_NAME`), `AddGroupCommandParser` will then call the `ParserUtil#parseGroupName()`
+ method to check for the validity of the input `GROUP_NAME`.
At this stage, `ParseException` would be thrown if the
+ `GROUP_NAME` specified is invalid.
+
+5. The `AddGroupCommandParser` then creates the `AddGroupCommand` based on the processed input.
+
+
+#### Command execution
+
+6. The `LogicManager` executes the `AddGroupCommand`.
+
+7. The `AddGroupCommand` calls the `Model#hasGroup()` to check if the group with the same `GROUP_NAME` has already existed in the group list.
+ `CommandException` would be thrown if there already existed a group with the same group name.
+
+8. The `AddGroupCommand` then calls the `Model#addGroup()` to add the input group to the list.
+
+#### Displaying of result
+
+9. Finally, the `AddGroupCommand` creates a `CommandResult` with a success message and return it to the `LogicManager` to complete the command execution.
+ The GUI would also be updated on this change and created a new dedicated group tab for this new group added.
+
+
+
+The following sequence diagram shows how the `addgroup` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `addgroup` command:
+
+
+
+
+
+## Edit group feature
+
+### About
+
+The edit group feature allows users to add or edit the Telegram invite link assigned for each group.
+`editgroup g/GROUP_NAME tg/TELEGRAM_LINK`.
+
+### How it is implemented
+
+The `editgroup` command mechanism is facilitated by the `EditGroupCommand` and the `EditGroupCommandParser`.
+It allows TA users to edit an existing group in TutorsContactsPro.
+It uses the `AddressBook#addGroup(Group group)` which is exposed in the `Model`
+
+Given below is the example usage scenario and how the add group mechanism behaves at each step.
+
+#### Parsing user input
+
+1. The users user inputs the `editgroup` command and provides the `GROUP_NAME` of the group in which the user wants to add or edit using the new `TELEGRAM_LINK` provided.
+
+2. The `TutorsContactsPro` then does preliminary processing to the input and creates a new `EditGroupCommandParser`.
+
+3. The `EditGroupCommandParser` then parses the user input and checks whether all the input attributes are present by checking the presence of the prefixes.
+ It also checks whether the command is in the correct format. In this case, the required prefix and attribute is `g/GROUP_NAME` and `tg/TELEGRAM_LINK`.
At this stage, if not all the prefixes are present,
+ `ParseException` would be thrown.
+
+4. If the required prefixes and attributes are present (i.e. `g/GROUP_NAME`), `EditGroupCommandParser` will then call the `ParserUtil#parseGroupName()`
+ method to check for the validity of the input `GROUP_NAME` and `TELEGRAM_LINK`.
At this stage, `ParseException` would be thrown if the
+ `GROUP_NAME` or `TELEGRAM_LINK` specified is invalid.
+
+5. The `EditGroupCommandParser` then creates the `EditGroupCommand` based on the processed input.
+
+
+#### Command execution
+
+6. The `LogicManager` executes the `EditGroupCommand`.
+
+7. The `EditGroupCommand` calls the `Model#hasGroup()` to check if the group with the `GROUP_NAME` already existed in the group list.
+ `CommandException` would be thrown if there are no group present with the same group name.
+
+8. The `EditGroupCommand` then calls the `Model#editroup()` to edit the input group's Telegram invite link.
+
+#### Displaying of result
+
+9. Finally, the `EditGroupCommand` creates a `CommandResult` with a success message and return it to the `LogicManager` to complete the command execution.
+
+
+
+The following sequence diagram shows how the `editgroup` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `editgroup` command:
+
+
+
+
+
+## Delete group feature
+
+### About
+
+The delete group feature allows users to delete an existing student group in
+the student group list via the command `deletegroup g/GROUP_NAME`.
+
+### How it is implemented
+
+The `deletegroup` command mechanism is facilitated by the `DeleteGroupCommand` and the `DeleteGroupCommandParser`.
+It allows users to delete an already-existing-group from the ArchDuke student group list. It uses the `AddressBook#removeGroup(Group key)`
+which is exposed in the `Model` interface as `Model#deleteGroup(Group target)`. Then, the `remove(Group toRemove)` is called on the `UniqueGroupList`
+to remove the group from the group list.
+
+Given below is the example usage scenario and how the delete group mechanism behaves at each step.
+
+#### Parsing user input
+
+1. The user inputs the `deletegroup` command and provide the `GROUP_NAME` of the group in which the user wants to remove.
+
+2. The `TutorsContactsPro` then does preliminary processing to the user input and creates a new `DeleteGroupCommandParser`.
+
+3. The `DeleteGroupCommandParser` then parses the user input and check whether all the input attributes are present by checking the presence of the prefixes.
+ It also checks whether the command is in the correct format. In this case, the required prefix and attribute is `g/GROUP_NAME`.
At this stage, if not all the prefixes are present,
+ `ParseException` would be thrown.
+
+4. If the required prefixes and attributes are present (i.e. `g/GROUP_NAME`), `DeleteGroupCommandParser` will then call the `ParserUtil#parseGroupName()`
+ method to check for the validity of the input `GROUP_NAME`.
At this stage, `ParseException` would be thrown if the
+ `GROUP_NAME` specified is invalid.
+
+5. The `DeleteGroupCommandParser` then creates the `DeleteGroupCommand` based on the processed input.
+
+#### Command execution
+
+6. The `LogicManager` executes the `DeleteGroupCommand`.
+
+7. The `DeleteGroupCommand` calls the `Model#getFilteredGroupList()` to get the current unmodifiable view of the filtered group list.
+
+8. The `DeleteGroupCommand` calls the `contains()` method on the obtained filtered group list to check if the group with the same `GROUP_NAME` existed in the group list.
+ `CommandException` would be thrown if there exists no group with the same group name.
+
+9. The `DeleteGroupCommand` then calls the `Model#deleteGroup()` to delete the input group to from the group list.
+
+#### Displaying of result
+
+10. Finally, the `DeleteGroupCommand` creates a `CommandResult` with a success message and return it to the `LogicManager` to complete the command execution.
+ The GUI would also be updated on this change and remove the dedicated group tab for the group deleted.
+
+
+
+The following sequence diagram shows how the `deletegroup` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `deletegroup` command:
+
+
+
+
+
+## Mail Command
+
+### About
+
+The Mail Command feature enables users to generate an email template prefilled with the email addresses of students filtered based on specified keywords.
+This command can be used to compose emails to these students directly from the user's default email client.
+
+### How it is Implemented
+
+The Mail Command feature is implemented using the `MailCommand` class and its corresponding parser, `MailCommandParser`.
+
+#### Command Structure
+
+The user inputs the `mail` command followed by optional keywords specifying groups of students they want to include in the email.
+
+#### Parsing Input
+
+1. The `MailCommandParser` parses the input arguments to extract the specified keywords.
+
+2. If no keywords are provided, an empty `MailCommand` is created, which results in the generation of a mailto link for all students.
+
+3. If keywords are provided, the parser validates them to ensure they conform to the expected format. If any keyword is invalid, a `ParseException` is thrown.
+
+4. The `MailCommand` with the appropriate predicate is then created using the `GroupContainsKeywordsPredicate`, which filters the students based on the specified keywords.
+
+#### Command Execution
+
+5. When the `MailCommand` is executed, it updates the filtered person list in the model based on the provided predicate.
+
+6. It then extracts the email addresses of the filtered students from the model.
+
+7. Using these email addresses, it generates a mailto link, concatenating them with semicolons to form a single string.
+
+#### Displaying Result
+
+8. Finally, the generated mailto link is encapsulated in a `CommandResult` object and returned to the logic manager for further handling.
+
+### Summary
+
+The Mail Command feature provides an efficient way for users to compose emails to specific groups of students directly from the application. By leveraging the power of filtering, it allows for targeted communication while maintaining simplicity and ease of use.
+
+
+
+The following sequence diagram illustrates how the `mail` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `mail` command:
+
+
+
+
+
+## Mailtg Command
+
+### About
+
+The Mailtg Command feature enables users to generate an email template containing the Telegram link for a specific group and prefilled with the email addresses of students filtered based on specified keywords.
+This command can be used to compose emails to these students directly from the user's default email client.
+
+## How it is Implemented
+
+The Mailtg Command feature is implemented using the `MailTelegramCommand` class and its corresponding parser, `MailTelegramCommandParser`.
+
+#### Command Structure
+
+The user inputs the `mailtg` command followed by the `/g` prefix and the keywords specifying groups of students they want to include in the email and the specific group's Telegram invite link.
+
+#### Parsing Input
+
+1. The `MailTelegramCommandParser` parses the input arguments to extract the specified keywords.
+
+2. If keywords are provided, the parser validates them to ensure they conform to the expected format. If any keyword is invalid, a `ParseException` is thrown.
+
+3. The `MailTelegramCommand` with the appropriate predicate is then created using the `GroupContainsKeywordsPredicate`, which filters the students based on the specified keywords.
+
+#### Command Execution
+
+4. When the `MailTelegramCommand` is executed, it updates the filtered person list in the model based on the provided predicate.
+
+5. It then extracts the email addresses of the filtered students from the model and the Telegram invite link assigned to the specific group.
+
+6. Using these email addresses and Telegram invite link, it generates a mailto link using the `createMailtoUrl` function.
+
+#### Displaying Result
+
+8. Finally, the generated mailto link is encapsulated in a `CommandResult` object and returned to the logic manager for further handling.
+
+### Summary
+
+The Mailtg Command feature provides an efficient way for users to compose emails to specific groups of students directly from the application. By leveraging the power of filtering, it allows for targeted communication while maintaining simplicity and ease of use.
+
+
+
+The following sequence diagram illustrates how the `mailtg` mechanism works:
+
+
+
+The following activity diagram summarizes what happens when a user executes the `mailtg` command:
+
+
+
+
+
### \[Proposed\] Undo/redo feature
#### Proposed Implementation
The proposed 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:
-* `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.
+* `VersionedAddressBook#commit()` — Saves the current major book state in its history.
+* `VersionedAddressBook#undo()` — Restores the previous major book state from its history.
+* `VersionedAddressBook#redo()` — Restores a previously undone major book state from its history.
These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial major book state, and the `currentStatePointer` pointing to that single major book state.
+
+
+
+Step 2. The user executes `delete 5` command to delete the 5th person in the major book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the major book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted major book state.
-![UndoRedoState0](images/UndoRedoState0.png)
+
-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.
+Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified major book state to be saved into the `addressBookStateList`.
-![UndoRedoState1](images/UndoRedoState1.png)
+
-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`.
+
-![UndoRedoState2](images/UndoRedoState2.png)
+**Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the major book state will not be saved into the `addressBookStateList`.
-
:information_source: **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 major book state, and restores the major book to that state.
-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.
+
-![UndoRedoState3](images/UndoRedoState3.png)
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook 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
+
+
+**Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook 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.
-
+
The following sequence diagram shows how an undo operation goes through the `Logic` component:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png)
+
+
+
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+**Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-
+
Similarly, how an undo operation goes through the `Model` component is shown below:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png)
+
-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.
+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 major book to that state.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook 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.
+
-
+**Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest major book state, then there are no undone AddressBook 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.
-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.
+
-![UndoRedoState4](images/UndoRedoState4.png)
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the major book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
-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. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+
-![UndoRedoState5](images/UndoRedoState5.png)
+Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all major book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+
+
The following activity diagram summarizes what happens when a user executes a new command:
-
+
#### Design considerations:
**Aspect: How undo & redo executes:**
-* **Alternative 1 (current choice):** Saves the entire address book.
+* **Alternative 1 (current choice):** Saves the entire major book.
* Pros: Easy to implement.
* Cons: May have performance issues in terms of memory usage.
@@ -237,15 +766,10 @@ The following activity diagram summarizes what happens when a user executes a ne
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
-
-### \[Proposed\] Data archiving
-
-_{Explain here how the data archiving feature will be implemented}_
-
-
--------------------------------------------------------------------------------------------------------------------
+
+
## **Documentation, logging, testing, configuration, dev-ops**
* [Documentation guide](Documentation.md)
@@ -256,48 +780,59 @@ _{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
+
+
## **Appendix: Requirements**
### Product scope
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
+* A computer science TA (Teaching Assistant) managing students for tutorials
+* has numerous of students to manage in a tutorial slot
+* has to add, list, delete, sort, search students in the app
* is reasonably comfortable using CLI apps
+* can type fast
+* can switch between different tutorial classes
+* prefer desktop apps over other types
+
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: manage students faster than a typical GUI driven app
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+| Priority | As a … | I want to … | So that I can… |
+|----------|-------------|---------------------------------------------------------|--------------------------------------------------------------------------------------------------|
+| `* * *` | TA | add new students to the app | keep track of their information |
+| `* * *` | TA | edit student profiles | keep their information up to date. |
+| `* * *` | TA | delete students from my class | track the existing number of students in my tutorial class |
+| `* * *` | TA | list all students in my class(es) | view all of my students’ details at one glance |
+| `* * *` | TA | search for specific students using keywords | quickly find relevant information |
+| `* * *` | TA | filter students according to their group | quickly find relevant information |
+| `* * *` | TA | mark attendance for a specific student | keep track of each student's attendance details |
+| `* * *` | TA | add a new group | keep track of the groups that i teach |
+| `* * *` | TA | add a Telegram link to each group | keep track of the Telegram invite links for each group that i teach |
+| `* * *` | TA | delete an existing group | track the existing number of groups that i currently teach |
+| `* * *` | TA | generate an email template | conveniently sent an email to the student recipients desired |
+| `* * *` | TA | generate an email template containing the Telegram link | conveniently sent an email containing the Telegram invite link to the student recipients desired |
+| `* *` | new TA user | be able to access a help window | easily seek help for the errors encountered |
-*{More to be added}*
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `TutorsContactsPro` and the **Actor** is the `Tutor`, unless specified otherwise)
-**Use case: Delete a person**
+**Use case: UC01 - Add a student**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. TA requests to list students
+2. System shows a list of students
+3. TA requests to add a specific student to the list
+4. System adds the student
Use case ends.
@@ -307,76 +842,647 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Use case ends.
-* 3a. The given index is invalid.
+* 3a. The add command parameters are invalid or incomplete.
- * 3a1. AddressBook shows an error message.
+ * 3a1. TutorsContactsPro shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+* 3b. TutorsContactsPro detects that the student already exists on the list.
-### Non-Functional Requirements
+ * 3b1. TutorsContactsPro informs the TA that the student already exists on the list.
+ * 3b2. TA confirms cancellation of adding the student.
+
+ Use case ends.
+
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-*{More to be added}*
+**Use case: UC02 - Edit a student**
-### Glossary
+**MSS**
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+1. TA requests to list students
+2. System shows a list of students
+3. TA requests to edits the particulars of the student
+4. System records the changes
---------------------------------------------------------------------------------------------------------------------
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorsContactsPro shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The edit command parameters are invalid or incomplete.
+
+ * 3b1. TutorsContactsPro shows an error message.
+
+ Use case resumes at step 2.
+
+
+**Use case: UC03 - Delete a student**
+
+**MSS**
+
+1. TA requests to list students
+2. System shows a list of students
+3. TA requests to delete a student
+4. System records the changes
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. TutorsContactsPro shows an error message.
+
+ Use case resumes at step 2.
+
+
+**Use case: UC04 - List all students**
+
+**MSS**
+
+1. TA requests to list students
+2. System shows a list of students
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+
+**Use case: UC05 - Find a student**
+
+**MSS**
+
+1. TA requests to list students
+2. System shows a list of students
+3. TA finds student(s) by keyword
+4. System shows a list of students matching the keyword
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given keyword is in an incorrect format (e.g., contains special characters not allowed, exceeds maximum length).
+
+ * 3a1. TutorsContactsPro shows an error message.
+ Use case resumes at step 2.
+
+* 4a. The list of search results is empty.
+
+ Use case ends.
+
+
+**Use case: UC06 - Filter students according to their group**
+
+**MSS**
+
+1. TA requests to list students
+2. System shows a list of students
+3. TA filters student(s) by keyword which is the group name desired
+4. System shows a list of students that belong to the group
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given keyword is in an incorrect format (e.g., contains special characters not allowed, exceeds maximum length, incorrect group name format).
+
+ * 3a1. TutorsContactsPro shows an error message.
+ Use case resumes at step 2.
+
+* 4a. The list of search results is empty.
+
+ Use case ends.
+
+
+**Use case: UC07 - Mark student's attendance**
+
+**MSS**
+
+1. TA requests to list students
+2. System shows a list of students
+3. TA marks the attendance of a specific student from a particular group during a lesson week
+4. System updates the attendance the student for that particular group accordingly
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given keyword is in an incorrect format (e.g., contains special characters not allowed, exceeds maximum length, incorrect group name format).
+
+ * 3a1. TutorsContactsPro shows an error message.
+ Use case resumes at step 2.
+
+
+**Use case: UC08 - Add a group**
+
+**MSS**
+
+1. TA requests to add a specific group
+
+2. System adds the group
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The addgroup command parameters are invalid or incomplete.
+
+ * 1a1. TutorsContactsPro shows an error message.
+
+ Use case resumes at step 2.
+
+* 1b. TutorsContactsPro detects that the group already exists on the list.
+
+ * 1b1. TutorsContactsPro informs the tutor that the group already exists on the list.
+ * 1b2. Tutor confirms cancellation of adding the group.
+
+ Use case ends.
+
+
+**Use case: UC09 - Add a Telegram link**
+
+**MSS**
+
+1. TA requests to add a specific Telegram link to a particular group
+2. System adds the Telegram link to the group
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given Telegram link is invalid or incomplete.
+
+ Use case resumes at step 1.
+
+
+**Use case: UC10 - Delete a group**
+
+**MSS**
+
+1. TA requests to delete a student
+2. System records the changes
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given group name parameter is invalid or incomplete.
+
+ * 1a1. TutorsContactsPro shows an error message.
+
+ Use case resumes at step 1.
+
+
+**Use case: UC011 - Generate email template**
+
+**MSS**
+
+1. TA requests generation of an email template for specific group(s)
+2. System shows the email template prefilled with emails of specific students recipients
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given group name parameter is invalid.
+
+ Use case resumes at step 1.
+
+
+**Use case: UC012 - Generate email template containing Telegram invite link**
+
+**MSS**
+
+1. TA requests generation of an email template to send the Telegram invite link for a specific group
+2. System shows the email template prefilled with emails of specific students recipients and the Telegram invite link for that particular group
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given group name parameter is invalid.
+
+ Use case resumes at step 1.
+
+
+
+### Non-Functional Requirements
+
+1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. 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.
+3. Should be able to list a maximum of 50 students within a single tutorial slot.
+4. Should be able to hold a maximum of 300 students in total without any significant decrease in performance.
+5. Any command should be visible within 3 seconds, ensuring a smooth and efficient user experience.
+6. The system should have an uptime of at least 99%, allowing tutors to access student information reliably at any time.
+7. Student important information (i.e name, email, Telegram handle, contact number) should be encrypted both in transit and at rest to prevent unauthorized access.
+8. The system should implement secure authentication mechanisms, such as multi-factor authentication, to verify the identity of users.
+9. TAs should only have access to student information for classes they are assigned to, ensuring data privacy.
+10. The system should be able to scale horizontally to accommodate an increase in the number of users and classes without compromising performance.
+11. Regular backups of the system database should be performed, with a robust disaster recovery plan in place to restore data in case of any unexpected failures or outages.
+
+
+### Glossary
+
+* **Mainstream OS**: Windows, Linux, Unix, MacOS
+* **TA**: TA (Teaching Assistant) refers to the person who teaches in a single tutorial/recitation/lab group.
+* **Student**: Student refers to an individual who attends a tutorial class taught by the tutor.
+* **Group**: Smaller classes in university which allow discussion of lecture content and assignment. This consists of tutorial, recitation and labs.
+* **CLI (Command-Line Interface)**: A text-based interface used to interact with the software by entering commands into a terminal or console window, typically preferred by users who prefer efficiency and automation.
+* **GUI (Graphical User Interface)**: A GUI is a user interface that employs graphical elements such as icons, buttons, and menus for user interaction, providing an intuitive and visually appealing way to navigate and use software.
+
+
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix: Instructions for manual testing**
+
+### Prefix summary
+These are the list of prefixes used in the commands for manual testing.
+
+| Prefix | Representation |
+|--------|-------------------|
+| `n/` | `NAME` |
+| `p/` | `PHONE_NUMBER` |
+| `e/` | `EMAIL` |
+| `y/` | `YEAR` |
+| `m/` | `MAJOR` |
+| `tg/` | `TELEGRAM_HANDLE` |
+| `r/` | `REMARKS` |
+| `g/` | `GROUP_NAME` |
+| `w/` | `WEEK` |
+| `a/` | `ATTENDANCE` |
-## **Appendix: Instructions for manual testing**
Given below are instructions to test the app manually.
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+
+
+**Note:** These instructions only provide a starting point for testers to work on;
testers are expected to do more *exploratory* testing.
-
+
+
### Launch and shutdown
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ i. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ ii. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-1. Saving window preferences
+2. Saving window preferences
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ i. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
+ ii. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
-### Deleting a person
+### Listing all students
+
+1. Listing all students
+
+ i. Test case: `list`
+ Expected: All students will be returned from the list. The number of students listed is shown in the status message. Timestamp in the status bar is updated.
+
+### Clearing data
-1. Deleting a person while all persons are being shown
+1. Clears all data in TutorsContactsPro
+
+ i. Test case: `clear`
+ Expected: All data is cleared from TutorsContactsPro.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+### Exiting TutorsContactsPro
- 1. 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.
+1. Exits TutorsContactsPro
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+ i. Test case: `exit`
+ Expected: Exits TutorsContactsPro, and all data is being saved.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+### Help
+
+1. Provides detailed information on how to use the different features in TutorsContactsPro
+
+ i. Test case: `help`
+ Expected: Help window pops up.
+
+ ii. Test case: Press the `F1` key
+ Expected: Similar to previous.
+
+### Adding a student
+
+1. Adding a student while student list is being shown
+
+ i. Prerequisites: List all students using the `list` command.
+
+ ii. Test case: `add n/John Doe p/98765432 e/johnd@example.com y/2 m/Computer Science tg/johndoe r/Very quiet student g/TUT04 g/LAB10 `
+ Expected: Adds a student with the name `John Doe` and the following attributes to the list. Details of added student shown in the status message.
+ * Phone number: 98765432,
+ * Email address: johnd@example.com
+ * Telegram handle: johndoe
+ * Year of study: 2
+ * Major: Computer Science
+ * Remarks: Very quiet student
+ * Groups: TUT04 and LAB10
+
+ iii. Test case : `add s/J@hn Doe m/A0123459G`
+ Expected: No student is added. Error details are shown in the status message.
+
+ iv. Test case : `add s/John Doe `
+ Expected: Similar to previous.
+
+
+### Deleting a student
+
+1. Deleting a student while all students are being shown
+
+ i. Prerequisites: List all students using the `list` command. There can be multiple students in the list.
+
+ ii. Test case: `delete 1`
+ Expected: First student is deleted from the list. Details of the deleted student is shown in the status message. Timestamp in the status bar is updated.
+
+ iii. Test case: `delete 0`
+ Expected: No student is deleted. Error details shown in the status message. Status bar remains the same.
+
+ iv. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+
+### Editing a student
+
+1. Edit a student to change their name/phone number/email address/year of study/major/Telegram handle/remark/group
+
+ 1. Prerequisites: List all students using the `list` command. There must be at least one student in the list.
+
+ 2. Test case: `edit 1 n/John`
+ Expected: First student's name is changed to John for all instances. Other attributes of the student remains the same. Details of the new student name is shown in the status message.
+
+ 3. Test case: `edit 0 n/John`
+ Expected: No student's details is changed. Error details shown in the status message.
+
+ 4. Other incorrect edit student commands to try: `edit x n/John`, edit 1 n/John p/12345678`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+### Finding student(s) by keyword(s)
+
+1. Finding a student while all students are being shown
+
+ i. Prerequisites: List all students using the `list` command. There can be multiple persons in the list.
+
+ ii. Test case: `find Al Yu`
+ Expected: Students who have any part of their names starting with `Al` or `Yu` like `Alex Yeoh` and `Bernice Yu` will be returned from the list. The number of contacts found is shown in the status message. Timestamp in the status bar is updated.
+
+ iii. Test case: `find Zoe`, assuming that there is no contact who has any part of his/her name starting with `Zoe`
+ Expected: No student is found. Error details shown in the status message. Status bar remains the same.
+
+ iv. Other incorrect find commands to try: `find`
+ Expected: Similar to previous.
+
+### Filtering student(s) by keyword(s)
+
+1. Filtering students while all students are being shown
+
+ i. Prerequisites: List all students using the `list` command. There can be multiple persons in the list.
+
+ ii. Test case: `filter TUT10`
+ Expected: Students who belongs to the group `TUT10` will be returned from the list. The number of contacts found is shown in the status message. Timestamp in the status bar is updated.
+
+ iii. Test case: `filter TUT01`, assuming that there is no contact who belongs to the group `TUT01`
+ Expected: No student is found. Error details shown in the status message. Status bar remains the same.
+
+ iv. Test case: `filter TU01`, where an invalid `KEYWORD/GROUP_NAME` is provided
+ Expected: No student is found. Error details shown in the status message. Status bar remains the same.
+
+ v. Other incorrect find commands to try: `filter`
+ Expected: Similar to previous.
+
+### Marking attendance
+
+1. Marking attendance of students while all students are being shown
+
+ i. Prerequisites: List all students using the `list` command. There can be multiple persons in the list.
+
+ ii. Test case: `mark 1 g/TUT05 w/1 a/P`
+ Expected: Marks the attendance of the 1st student in the list. Marks `TUT05` week 1 attendance as present. Details updated in the status message. Timestamp in the status bar is updated.
+
+ iii. Test case: `mark 1 g/TUT01 w/1 a/P`, assuming that there is no contact who belongs to the group `TUT01`
+ Expected: No student is found. Error details shown in the status message. Status bar remains the same.
+
+ iv. Test case: `mark 1 g/T05 w/1 a/P`, where an invalid `GROUP_NAME` is provided
+ Expected: Error details shown in the status message. Status bar remains the same.
+
+ v. Test case: `mark 1 g/TUT05 w/0 a/P`, where an invalid week number is provided
+ Expected: Error details shown in the status message. Status bar remains the same.
+
+ vi. Test case: `mark 1 g/TUT05 w/1 a/B`, where an invalid `ATTENDANCE` field is provided
+ Expected: Error details shown in the status message. Status bar remains the same.
+
+ vii. Other incorrect find commands to try: `mark`
+ Expected: Similar to previous.
+
+### Adding a group
+
+1. Adding a group that has yet to exist in TutorsContactsPro.
+
+ i. Prerequisites: List all lessons using the `list` command.
+
+ ii. Test case: `addgroup g/LAB12` where group `LAB12` does not exist in TutorsContactsPro
+ Expected: Adds the group `LAB12` to TutorContactsPro. A new group tab labelled `LAB12` is added. Details of the studio added shown in status message.
+
+ iii. Test case: `addgroup g/RE1` where an invalid `GROUP_NAME` is provided
+ Expected: No group added. Error details shown in the error message.
+
+ iv. Test case: `addgroup g/TUT05` where group `TUT05` already exists in TutorsContactsPro
+ Expected: No group added because the group with the exact same name already exists.
+ Error details shown in the error message.
+
+ v. Other incorrect add studio commands to try: `addgroup`, `addgroup g/`, `...`
+ Expected: Similar to previous.
+
+### Editing a group
+
+1. Editing a group that exists in TutorsContactsPro.
+
+ i. Prerequisite: List all lessons using the `list` command.
+
+ ii. Test case: `editgroup g/TUT05 tg/https://t.me/abcdefg` where group `TUT05` exists in TutorsContactsPro
+ Expected: The group with the name `TUT05` is edited and the Telegram invite link `https://t.me/abcdefg` is assigned to group `TUT05`.
+ The details of the edited group is shown in the success message.
+
+ iii. Test case: `editgroup g/RE1 tg/https://t.me/abcdefg` where an invalid `GROUP_NAME` is provided
+ Expected: No group edited. Error details shown in the error message.
+
+ iv. Test case: `editgroup g/TUT01 tg/https://t.me/abcdefg` where group `TUT01` does not exist in TutorsContactsPro
+ Expected: No group edited because the group does not exist.
+ Error details shown in the error message.
+
+ iv. Test case: `editgroup g/TUT05 tg/` where no telegram invite link is provided
+ Expected: No group edited. Error details shown in the error message.
+
+ v. Other incorrect add studio commands to try: `editgroup`, `editgroup g/ tg/`, `...`
+ Expected: Similar to previous.
+
+### Deleting a group
+
+1. Deleting a group that exists in TutorsContactsPro.
+
+ i. Prerequisite: List all lessons using the `list` command.
+
+ ii. Test case: `deletegroup g/TUT05` where group `TUT05` exists in TutorsContactsPro
+ Expected: The group with the name `TUT05` is deleted from TutorsContactsPro.
+ The group tab `TUT05` is removed. The details of the deleted group is shown in the success message.
+
+ iii. Test case: `deletegroup g/RE1` where an invalid `GROUP_NAME` is provided
+ Expected: No group deleted. Error details shown in the error message.
+
+ iv. Test case: `deletegroup g/TUT01` where group `TUT01` does not exist in TutorsContactsPro
+ Expected: No group deleted because the group does not exist.
+ Error details shown in the error message.
+
+ v. Other incorrect add studio commands to try: `addgroup`, `addgroup g/`, `...`
+ Expected: Similar to previous.
+
+### Generating email template
+
+1. Generating prefilled email template containing the email addresses of students based on specific `GROUP NAME` entered.
+
+ i. Prerequisite: List all lessons using the `list` command.
+
+ ii. Test case: `mail TUT05` where group `TUT05` exists in TutorsContactsPro
+ Expected: Generates a email template containing email addresses of all students belonging to `LAB05`
+
+ iii. Test case: `mail RE1` where an invalid `GROUP_NAME` is provided
+ Expected: No email template generated. Error details shown in the error message.
+
+ iv. Test case: `mail TUT01` where group `TUT01` does not exist in TutorsContactsPro
+ Expected: No email template generated. Error details shown in the error message.
+
+ v. Other incorrect add studio commands to try: `mail`, `...`
+ Expected: Similar to previous.
+
+### Generating email template with Telegram link
+
+1. Generates a prefilled email template, including the Telegram link for that specific group and email addresses of student recipients from the specified group.
+
+ i. Prerequisite: List all lessons using the `list` command.
+
+ ii. Test case: `mailtg g/TUT05` where group `TUT05` exists in TutorsContactsPro
+ Expected: Generates a email template containing the Telegram invite link for group `TUT05` and email addresses of all students belonging to `LAB05`
+
+ iii. Test case: `mailtg g/RE1` where an invalid `GROUP_NAME` is provided
+ Expected: No email template generated. Error details shown in the error message.
+
+ iv. Test case: `mailtg g/TUT01` where group `TUT01` does not exist in TutorsContactsPro
+ Expected: No email template generated. Error details shown in the error message.
+
+ v. Other incorrect add studio commands to try: `mailtg`, `mailtg g/`, `...`
+ Expected: Similar to previous.
+
### Saving data
-1. Dealing with missing/corrupted data files
+1. Dealing with missing data file(s).
+
+ i. Prerequisites: If there exists any addressbook.json in the data folder at the root of the application directory, delete the file.
+
+ ii. Test case: Double-click on the jar file to run the application.
+ Expected: Application runs and loads the sample data from `SampleStudentUtil#getSampleStudentBook`, `SampleTaskUtil#getSampleTaskBook` and/or `SampleLessonUtil#getSampleLessonBook`.
+
+2. Dealing with corrupted data file(s).
+
+ i. Prerequisites: Modify the addressbook.json to be an illegal format, such as deleting the “name” field of a student, "name" of a group etc.
+
+ ii. Test case: Double-click on the jar file to run the application.
+ Expected: Application runs and has no data on initial load. Running the next command overwrites the current corrupted json file(s).
+
+
+## **Appendix: Planned Enhancements**
+Given below are the planned enhancements for the application.
+
+**Team size: 4**
+
+1. **Improve add feature**: Currently, TutorContactsPro allows the addition of students with the same exact details and names but different case for names which can be misleading. This can result in addition of duplicate students instead. Hence, we plan to improve the `add` feature to detect whether students added have completely similar details regardless of casing and throw a duplicate student added warning where appropriate.
+
+2. **Allow the display of Telegram invite link**: TutorsContactsPro currently only allows the assignment of the Telegram invite link to a specific group via the `editgroup` command which is not too informative. We plan to improve the interface to include a field to display the Telegram invite link assigned for each specific group.
+
+3. **Improve the group tabs**: After marking the attendance in a particular group tab, TutorsContactsPro currently will automatically direct users to the results tab which can result in inconvenience for users. We plan to improve the group tabs interface such that users will still be able to remain on their selected group tab aftering executing any commands.
+
+4. **Improve add Telegram link feature**: Currently, TutorsContactsPro only allows the assignment of a Telegram invite link to a specific group via the `editgroup` command which may not be very intuitive for users. We plan to improve this feature by combining it with the `addgroup` feature to allow addition of a group together with the Telegram invite link.
+
+5. **Add unmark attendance feature**: TutorsContactsPro currently allows TA users to mark the attendance for a particular student which can be inconvenient when users make a mistake marking the attendance and they are unable to undo it. Therefore, in the future, we plan to include an additional unmark attendance feature for a specified student.
+
+6. **Improve generation of email template command**: The current `mail` command only takes in the `GROUP_NAME` input with no prefixes whereas the `mailtg` command takes in a `g/` prefix with a `GROUP_NAME` input. The difference in the command format for these two closely related commands can make it complex for users. Therefore, we plan to simplify and standardise the command input for `mail` and `mailtg` to be `g/GROUP_NAME`.
+
+7. **Improve generation of email template command**: When executing mail, mailtg command with an invalid group, including an non-existing group, empty group and group without Telegram invite link. TutorsContactsPro currently does not display a warning for users. We plan to improve them such that a warning message will be displayed when the input provided is invalid.
+
+8. **Make filter feature error message more specific**: When filtered with a non-existing group, the current error message displays`0 students listed!` which is too general. We plan to `filter` command check whether the `GROUP_NAME` provided is an existing group. The error message will then also mention the reason for failing to filter any students: `Group does not exist, 0 students listed!`
+
+
+
+## **Appendix: Effort**
+
+### **Difficulty Level**
+
+Developing TutorsContactsPro presented several challenges for our team, ranging from moderate to high difficulty. These obstacles arose due to various factors:
+
+- We embarked on the project with an existing codebase, a brownfield project, which we were not initially familiar with. This required us to invest significant time and effort in understanding the intricacies of the codebase.
+- Prior to making any substantial progress, we had to dedicate considerable time to comprehensively grasp the entire model and setup of AB3, upon which our project was built, in order to effectively refactor it.
+- Given that it was our team's first hand experience into working with Java and JavaFX within a software engineering framework, there was a steep learning curve that we had to overcome.
+- In the process of development, we introduced a new model component, namely `Group`, and subsequently implemented additional commands to facilitate the modification of group information and associated fields.
+- Furthermore, adjustments were necessary to tailor the existing `person` model to suit the specific requirements of our application, which entailed modifying and augmenting certain fields.
+- Building upon the foundation of existing AB3 features, we also undertook the creation of supplementary functionalities. These included the development of features such as generating email templates and marking students' attendance.
+
+Despite these challenges, our team remained committed and ultimately succeeded in delivering TutorsContactsPro, a robust and feature-rich application.
+
+### **Challenges Faced**
+We had encountered the following challenges during the process of building TutorsContactsPro:
+
+- Refactoring the AB3 codebase to fit TutorsContactsPro requirements too more time than anticipated due to the size of AB3's codebase.
+- Feature like marking student's attendance and generating email template were more complex and time-consuming than anticipated.
+- When we worked on closely related features, there were certain time-consuming merge conflicts to be resolved. In addition, this also created greater confusion in our work.
+- We were unable to merge certain pull requests successfully and had to pull back on our original plans due to time limitations.
+
+### **Effort Required**
+Due to the above difficulties and challenges faced, in addition to our implementation of relatively complex features including generating email templates, marking student's attendance and generating group tabs for GUI,
+a considerable amount of effort was put in to manage the additional dependencies and conduct additional unit/integration testing.
+
+### **Achievements**
+
+- Built a comprehensive but easy-to-understand user interface
+- Generate a relatively user-friendly interface with inclusion of sufficient and comprehensive CLI command and GUI design
+- TutorsContactsPro is comprehensive, storing essential student information for users
+- TutorsContactsPro also addresses the main pain points for TA users with the introduction of new features such as generating email template and marking attendance for students
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-1. _{ more test cases … }_
diff --git a/docs/Documentation.md b/docs/Documentation.md
index 3e68ea364e7..082e652d947 100644
--- a/docs/Documentation.md
+++ b/docs/Documentation.md
@@ -1,29 +1,21 @@
---
-layout: page
-title: Documentation guide
+ layout: default.md
+ title: "Documentation guide"
+ pageNav: 3
---
-**Setting up and maintaining the project website:**
-
-* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation.
-* The `docs/` folder is used for documentation.
-* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html).
-* Note these points when adapting the documentation to a different project/product:
- * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar.
- * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format).
-* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping)
+# Documentation Guide
+* We use [**MarkBind**](https://markbind.org/) to manage documentation.
+* The `docs/` folder contains the source files for the documentation website.
+* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation.
**Style guidance:**
* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style).
+* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html).
-* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-**Converting a document to the PDF format:**
+**Converting to PDF**
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
+* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html).
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index c8385d85874..00000000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
-
-gem 'jekyll'
-gem 'github-pages', group: :jekyll_plugins
-gem 'wdm', '~> 0.1.0' if Gem.win_platform?
-gem 'webrick'
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index e913ddab952..c6f2705c257 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -52,7 +52,7 @@ GEM
jekyll-relative-links (= 0.6.1)
jekyll-remote-theme (= 0.4.3)
jekyll-sass-converter (= 1.5.2)
- jekyll-seo-tag (= 2.8.0)
+ jekyll-seo-group (= 2.8.0)
jekyll-sitemap (= 1.4.0)
jekyll-swiss (= 1.0.0)
jekyll-theme-architect (= 0.2.0)
@@ -145,51 +145,51 @@ GEM
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
- jekyll-seo-tag (2.8.0)
+ jekyll-seo-group (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-swiss (1.0.0)
jekyll-theme-architect (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-cayman (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-dinky (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-hacker (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-leap-day (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-merlot (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-midnight (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-minimal (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-modernist (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-primer (0.6.0)
jekyll (> 3.5, < 5.0)
jekyll-github-metadata (~> 2.9)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-slate (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-tactile (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-theme-time-machine (0.2.0)
jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
+ jekyll-seo-group (~> 2.0)
jekyll-titles-from-headings (0.5.3)
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
@@ -211,7 +211,7 @@ GEM
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
- jekyll-seo-tag (~> 2.1)
+ jekyll-seo-group (~> 2.1)
minitest (5.19.0)
nokogiri (1.16.2)
mini_portile2 (~> 2.8.2)
diff --git a/docs/Logging.md b/docs/Logging.md
index 5e4fb9bc217..589644ad5c6 100644
--- a/docs/Logging.md
+++ b/docs/Logging.md
@@ -1,8 +1,10 @@
---
-layout: page
-title: Logging guide
+ layout: default.md
+ title: "Logging guide"
---
+# Logging guide
+
* We are using `java.util.logging` package for logging.
* The `LogsCenter` class is used to manage the logging levels and logging destinations.
* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..7bcc1c39381 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -1,29 +1,34 @@
---
-layout: page
-title: Setting up and getting started
+ layout: default.md
+ title: "Setting up and getting started"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Setting up and getting started
+
+
--------------------------------------------------------------------------------------------------------------------
## Setting up the project in your computer
-
:exclamation: **Caution:**
+
+**Caution:**
Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
-
+
First, **fork** this repo, and **clone** the fork into your computer.
If you plan to use Intellij IDEA (highly recommended):
1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**.
-1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
+1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
+
+ Note: Importing a Gradle project is slightly different from importing a normal Java project.
+
1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
+ 1. Run the `seedu.major.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
--------------------------------------------------------------------------------------------------------------------
@@ -34,10 +39,11 @@ If you plan to use Intellij IDEA (highly recommended):
If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours.
-
:bulb: **Tip:**
+
+ **Tip:**
Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-
+
1. **Set up CI**
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..f2c8b19758c 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -1,12 +1,15 @@
---
-layout: page
-title: Testing guide
+ layout: default.md
+ title: "Testing guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Testing guide
---------------------------------------------------------------------------------------------------------------------
+
+
+
+
## Running tests
@@ -19,8 +22,10 @@ There are two ways to run tests.
* **Method 2: Using Gradle**
* Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`)
-
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
-
+
+
+**Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
+
--------------------------------------------------------------------------------------------------------------------
@@ -29,8 +34,8 @@ There are two ways to run tests.
This project has three types of tests:
1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
+ e.g. `seedu.major.commons.StringUtilTest`
1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
+ e.g. `seedu.major.storage.StorageManagerTest`
1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
+ e.g. `seedu.major.logic.LogicManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 7abd1984218..d2ffe2069c0 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,198 +1,773 @@
---
-layout: page
-title: User Guide
+layout: default.md
+title: "User Guide"
+pageNav: 3
---
+![Logo](images/TutorsContactsProLogo.png)
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
-* Table of Contents
-{:toc}
+## Table of Contents
+
+* [Introduction](#feature-introduction)
+
+ * [About TutorsContactsPro](#feature-about)
+
+ * [About the User Guide](#feature-aboutUG)
+
+ * [Navigating the User Guide](#navigating-the-user-guide)
+
+* [Getting Started](#feature-gettingStarted)
+
+ * [Installing and Launching TutorsContactsPro](#feature-install)
+
+ * [Learning about TutorsContactsPro components](#feature-learn)
+
+ * [Understanding the User Interface (UI)](#feature-ui)
+
+ * [Quick Start](#feature-quickStart)
+
+* [Command Notes](#feature-features)
+
+* [Basic Commands](#feature-basicCommands)
+
+ * [Viewing help: `help`](#feature-help)
+
+ * [Listing all students: `list`](#feature-list)
+
+ * [Clearing all entries: `clear`](#feature-clear)
+
+ * [Exiting the program: `exit`](#feature-exit)
+
+* [Student Commands](#feature-studentCommands)
+
+ * [Adding a student: `add`](#feature-add)
+
+ * [Editing a student: `edit`](#feature-edit)
+
+ * [Locating students using keyword: `find`](#feature-find)
+
+ * [Filtering students using group: `filter`](#feature-filter)
+
+ * [Marking attendance of a student in the group: `mark`](#feature-mark)
+
+ * [Deleting a student: `delete`](#feature-delete)
+
+* [Group Commands](#feature-groupCommands)
+
+ * [Adding a group: `addgroup`](#feature-addgroup)
+
+ * [Editing a group: `editgroup`](#feature-editgroup)
+
+ * [Deleting a group: `deletegroup`](#feature-deletegroup)
+
+ * [Generating email template: `mail`](#feature-mail)
+
+ * [Generating email template for Telegram link: `mailtg`](#feature-mailtg)
+
+* [FAQ](#feature-faq)
+
+* [Known issues](#feature-issues)
+
+* [Planned Enhancements](#feature-enhancements)
+
+* [Command Summary](#feature-summary)
+
+* [Appendix A: Installing Java](#feature-appendix-a-installing-java)
--------------------------------------------------------------------------------------------------------------------
+# Introduction
+
+
+
+# About TutorsContactsPro
+
+Unlock the full potential of your teaching journey with TutorsContactsPro, the ultimate desktop tool designed for **NUS tutors and teaching assistants in Computer Science courses**.
+
+Seamlessly navigate through student information with our intuitive blend of **Command Line Interface (CLI)** and **Graphical User Interface (GUI)**.
+
+From effortless attendance tracking to personalized student profiles, TutorsContactsPro streamlines administrative tasks for maximum efficiency.
+
+Experience the future of teaching management today with TutorsContactsPro!
+
+
+## About the User Guide
+
+This user guide aims to provide you with the information needed to get started with TutorsContactsPro.
+
+Feel free to turn back to this user guide if you encounter any problems or doubts while using TutorsContactsPro.
-## Quick start
+* If you are **new** to TutorsContactsPro, you may refer to the **[Navigating the User Guide](#navigating-the-user-guide)** section first, then to the **[Getting Started](#getting-started)** section to start installing and using TutorsContactsPro.
-1. Ensure you have Java `11` or above installed in your Computer.
+* If you would like to find out more about its **individual features**, you may refer to the **[Command Notes and Features](#feature-features)** section.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+* If you are an **experienced user** and want to quickly refer to the various commands, you may refer to the **[Command Summary](#feature-summary)** section.
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+* If you would like to find out more about the **technical aspects of TutorsContactsPro**, you may refer to our **[Developer Guide](https://ay2324s2-cs2103t-W10-4.github.io/tp/DeveloperGuide.html)**.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+* Finally, if you would like an **overview** of this entire user guide, you may refer to our **[Table of Contents](#table-of-contents)**.
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
- * `list` : Lists all contacts.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+## Navigating the User Guide
+Throughout this user guide, the following symbols and keywords will be utilized for your convenience.
- * `clear` : Deletes all contacts.
+| Icons/Syntax | Meaning |
+|--------------|--------------------------------------------|
+| ℹ️ | Information that you should take note of |
+| 💡 | Useful advice and tips |
+| ⚠️ | Warnings you should read before proceeding |
- * `exit` : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+Below is the glossary for important technical terms that you may find useful.
+
+| Technical terms | Meaning |
+|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| CLI | CLI stands for Command Line Interface. It is a text-based interface used for interacting with computer systems and executing commands by typing them into a terminal window. |
+| GUI | GUI stands for Graphical User Interface, a visual way of interacting with computers using icons and menus instead of text commands. |
+| Command | Command refers to a directive or instruction given to a computer system to perform a specific task or function. |
+| GUI component | GUI component refers to an element or part of a graphical user interface (GUI) that allows users to interact with the software application. |
+| UI | A User Interface (UI) refers to the platform or system through which users interact with software applications or electronic devices, encompassing elements like graphical interfaces, command-line interfaces, and voice-controlled interfaces. |
+| Interface | An interface is a platform or system that allows users to interact with software applications or electronic devices, aiming to create intuitive and user-friendly experiences. |
+| Domain | A domain, in the context of the internet, refers to a unique address used to identify a particular website or network. E.g. for the email address `username@example.com`, the domain is `example.com`. |
+| Top-Level Domain (TLD) | The top-level domain (TLD) is the part of a website's address that comes after the last dot. It shows what kind of website it is. E.g. for the email address `username@example.com`, the TLD is `.com`. |
+
+[Table Of Contents](#table-of-contents)
--------------------------------------------------------------------------------------------------------------------
-## Features
-
+# Getting Started
+
+## Installing and Launching TutorsContactsPro
+
+### Step 1: Install Java
+
+Ensure you have Java `11` or above installed on your computer. If you don't have Java installed, follow the instructions in [Appendix A: Installing Java](#appendix-a-installing-java).
+
+### Step 2: Download
+
+Download the latest `TutorsContactsPro.jar` from [here](https://github.com/AY2324S2-CS2103T-W10-4/tp/releases).
-**:information_source: Notes about the command format:**
+### Step 3: Set up TutorsContactsPro Home Folder
-* 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`.
+Copy or move the downloaded `TutorsContactsPro.jar` file to the folder you want to use as the _home folder_ for your TutorsContactsPro.
-* 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 ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+### Step 4: Launch TutorsContactsPro
-* 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.
+1. Open a terminal (MacOS) or command prompt (Windows).
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+2. Navigate to the folder where you placed the `TutorsContactsPro.jar` file using the `cd` command. For example, John Doe has his `TutorsContactsPro.jar` file in `/Users/JohnDoe/Desktop/TutorsContactsPro`, so he'll run:
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-
+ ```bash
+ cd /Users/JohnDoe/Desktop/TutorsContactsPro
+ ```
-### Viewing help : `help`
+3. If you are using terminal, verify that your working directory contains `TutorsContactsPro.jar` by using the `ls` command.
+Else if you are using command prompt, verify that your working directory contains `TutorsContactsPro.jar` by using the `dir` command.
+You should see `TutorsContactsPro.jar` in the list of files printed. Here's what our John Doe got when running this command.
-Shows a message explaning how to access the help page.
+ ```bash
+ JohnDoe@johndoe TutorsContactsPro % ls
+ TutorsContactsPro.jar
+ ```
+4. Once your terminal's working directory is the folder that contains `TutorsContactsPro.jar`, run the following command to launch TutorsContactsPro:
+
+ ```bash
+ java -jar TutorsContactsPro.jar
+ ```
+
+ After executing this command, a graphical user interface (GUI) similar to the one below should appear shortly:
+
+
+
+
+Note: The application comes with some sample data for your reference.
+
+
+
+---
+
+## Learning about TutorsContactsPro components
+
+---
+
+### Students
+A 'Student' is an individual that you will store in TutorsContactsPro.
+It is an individual that attends one or more [`Groups`](#group) that you teach.
+TutorsContactsPro allows you to store essential information or attributes about the student and remarks for them.
+
+#### Attributes:
+
+| Parameter | Representation | Prefix | Constraints |
+|-------------------|------------------------------------------|--------|-----------------------------------------------------------------------------------------|
+| `NAME` | Name of the student | `n/` | Auto-capitalization will be handled. Extra/trailing/leading spaces will be removed |
+| `PHONE_NUMBER` | Phone number of the student | `p/` | Must be in numerical format with no limitations on the number of digits |
+| `EMAIL` | Email of the student | `e/` | Must be in email format `email username`@`domain` but can exclude top-level domain(TLD) |
+| `YEAR` | Academic Year of the student | `y/` | A number ranging from 1 - 6, inclusive |
+| `MAJOR` | Academic Major of the student contact | `m/` | String to represent the major |
+| `TELEGRAM_HANDLE` | Telegram handle of the student | `tg/` | Telegram handle format (a-z, 0-9 and underscores, case-insensitive), without prefix “@” |
+| `REMARKS` | Additional remarks of the student | `r/` | A case-sensitive string. This can be anything |
+| `GROUP_NAME` | Tutorial/Recitation/Lab slot | `g/` | Must be in correct slot format `TUT/REC/LAB` + `2-digit number` |
+
+### Group
+
+A `Group` is a lesson that is taught by you and attended by students.
+It can only strictly be a Tutorial/Recitation/Lab.
+
+#### Attributes:
+
+| Parameter | Representation | Prefix | Constraints |
+|-----------------|----------------------------|---------|---------------------------------------------------------------------------------|
+| `GROUP_NAME` | Name of the group | `g/` | Group name must begin with `TUT/REC/LAB` + `2-digit number` |
+| `TELEGRAM_LINK` | Telegram link of the group | `tg/` | Must be a valid Telegram group link beginning with `https://t.me` |
+| `ATTENDANCE` | Attendance of the student | `a/` | A string array representing the attendance, initially automatically constructed |
+
+
+## Understanding the User Interface (UI)
+TutorsContactsPro's UI consists of 4 main parts that you will be using:
+
+### Menu Bar
+* In the menu bar, you can effortlessly exit TutorsContactsPro or access the help window with just a click of your mouse!
+* Fear not if you are not well-versed in commands, the menu bar offers a beginner-friendly alternative to exit TutorsContactsPro or open the help window.
+
+### Help
+Upon clicking help on the menu bar or by using the shortcut `F1`, you will be assisted with a help window pop-up.
+More is to be explained in [`help`](#feature-help) feature.
+
+### Command Panel
+* The command panel where you will input commands and receive their corresponding results.
+* You can type in your command in the upper box.
+* And use the lower box to view the results of those commands.
+
+### Student Panel
+* The Student panel is a panel that you can use to see the list of students stored in TutorsContactsPro.
+* You can see student's name, phone number, year of study, major, email address, group, Telegram handle and even whatever remarks that you have for each of them as needed!
+
+The following image displays the layout of a student panel.
+![Student Panel](images/StudentPanel.png)
+
+
+
+### Result and Group Tabs
+* There are labelled tabs below the `Student Details` header.
+* The leftmost `Results` tab will display the list of all student contacts that are taught by you.
+* The subsequent tabs are labelled with their respective group names. These tabs display the attendance table details of students that belong to the specific group labelled by that tab.
+
+The following image displays the layout of the group tabs.
+![GroupTabs](images/GroupTabs.png)
+
+The following image summarises the overall layout of TutorsContactsPro's User Interface(UI).
+![UI layout](images/UILayout.png)
+
+
+
+## Quick start
+Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+Some example commands you can try:
+
+* [`list`](#feature-list) : Lists all students.
+
+* [`clear`](#feature-clear) : Deletes all students on the list.
+
+* [`exit`](#feature-exit) : Exits the app.
+
+* [`add n/John Doe p/98765432 e/johnd@example.com y/2 m/Computer Science tg/johndoe r/Very quiet student g/TUT04 `](#feature-add) : Adds a student named `John Doe` to the list.
+
+* [`edit 1 p/93840823 y/3 tg/jiejfh203`](#feature-edit) : Edits the first student on the current list.
+
+* [`find John Tan`](#feature-find) : Lists all the students with names that matches 'John' or 'Tan'.
+
+* [`filter TUT10`](#feature-filter) : Lists all the students in group 'TUT10'
+
+* [`mark 1 g/TUT05 w/1 a/P`](#feature-mark) : Marks the attendance of the first student in group 'TUT05' as present in TutorsContactsPro.
+
+* [`delete 3`](#feature-delete) : Deletes the third student shown in the current list.
+
+* [`addgroup g/TUT01`](#feature-addgroup) : Adds the group 'TUT01' to TutorsContactsPro.
+
+* [`editgroup g/TUT01`](#feature-editgroup) : Edits the group 'TUT01' in TutorsContactsPro.
+
+* [`deletegroup g/TUT01`](#feature-deletegroup) : Deletes the group 'TUT01' in TutorsContactsPro.
+
+* [`mail`](#feature-mail) : Generates a prefilled email template sent to emails of all students on the list.
+
+* [`mailtg g/TUT01`](#feature-mailtg) : Generates a prefilled email template containing the Telegram link for 'TUT01', with the recipient field prefilled with students of 'TUT01'.
+
+
+Refer to the [Command Notes and Features](#feature-features) below for details of each command.
+
+[Table Of Contents](#table-of-contents)
+
+--------------------------------------------------------------------------------------------------------------------
+
+
+## Command Notes and Features
+
+[//]: # ()
+
+**Notes about the command format:**
+
+| Command format | Representation | Examples |
+|-----------------------|---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
+| `UPPER_CASE` | Words in `UPPER_CASE` are the parameters to be supplied by the user | In `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe` |
+| square brackets `[]` | Items in square brackets are optional | `n/NAME [g/Group]` can be used as `n/John Doe g/TUT07` or as `n/John Doe` |
+| `…` | Items with `…` after them can be used multiple times including zero times | `[g/GROUP]…` can be used as `g/TUT03`, `g/LAB01`, `g/REC08`, etc. |
+| Order | Parameters can be in any order | If the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable |
+| Extraneous parameters | Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored | If the command specifies `help 123`, it will be interpreted as `help` | | Singapore phone number, 8 digits, without country code |
+
+
+> **⚠️Warning:**
+If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+
+
+## Basic Commands
+This section contains the basic commands of TutorsContactsPro.
+
+
+### Viewing help : `help`
+
+Allows you to easily access detailed information on how to use the different features in TutorsContactsPro.
![help message](images/helpMessage.png)
Format: `help`
+> ℹ️ **Information:**
+> * Strictly enter only the `help` command. Any inputs after `help` will be ignored.
+ E.g. When `help 44` is entered, the `44` input following `help` will be ignored and the `help` command will still be executed.
+
+### Listing all students : `list`
+
+Shows a list of all your students, so that you can conveniently view all their details.
+
+Format: `list`
-### Adding a person: `add`
+> 💡**Tips:**
+Auto-capitalization will be handled. Extra/trailing/leading spaces will be removed.
+> ℹ️ **Information:**
+> * Strictly enter only the `list` command. Any inputs after `list` will be ignored.
+ E.g. When `list 44` is entered, the `44` input following `list` will be ignored and the `list` command will still be executed.
-Adds a person to the address book.
+
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+### Clearing all entries : `clear`
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+Clears all student entries from TutorsContactsPro, so that you can always start afresh in the next teaching semester.
+
+Format: `clear`
+
+> ℹ️ **Information:**
+> * Strictly enter only the `clear` command. Any inputs after `clear` will be ignored.
+ E.g. When `clear 44` is entered, the `44` input following `clear` will be ignored and the `clear` command will still be executed.
+
+### Exiting the program : `exit`
+
+Exits the program and your changes will be saved automatically.
+
+Format: `exit`
+
+
+
+## Student Commands
+
+This section contains commands for managing students in TutorsContactsPro.
+
+### Adding a student: `add`
+
+Adds a student to the list. This way, you can keep track of all student details easily.
+
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL y/YEAR m/MAJOR tg/TELEGRAM_HANDLE [r/REMARK] [g/GROUP]…`
+
+> ℹ️ **Information:**
+> * Allows addition of multiple groups to a student.
+> * Allows optional addition of a single remark or group(s) to a student.
+> * Allows addition of student emails with domain names, excluding the need for top-level domains(TLD) such as .com or .sg as email can be hosted on a local machine.
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `add n/John Doe p/98765432 e/johnd@example.com y/2 m/Computer Science tg/johndoe r/Very quiet student g/TUT04 g/LAB05`
+ will add a student named John Doe to your student list.
+ * Phone number: 98765432
+ * Email address: johnd@example.com
+ * Telegram handle: johndoe
+ * Year of study: 2
+ * Major: Computer Science
+ * Remark: Very quiet student
+ * Group(s): TUT04 and LAB05
-### Listing all persons : `list`
+* `add n/Kendra Huetta p/98765367 e/Kendra@example.com y/1 m/Computer Science tg/KendraHuetta r/quiet student g/LAB05`
+ will add a student named Kendra Huetta, to your student list, as shown in the image below.
+ * Phone number: 98765367
+ * Email address: Kendra@example.com
+ * Telegram handle: KendraHuetta
+ * Year of study: 1
+ * Major: Computer Science
+ * Remark: quiet student
+ * Group(s): LAB05
-Shows a list of all persons in the address book.
+![result for 'add Kendra Huetta'](images/addFeature.png)
-Format: `list`
+
-### Editing a person : `edit`
+### Editing a student : `edit`
-Edits an existing person in the address book.
+Edits an existing student you have selected. This way, you can always keep student details recorded up to date.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [y/YEAR] [m/MAJOR] [tg/TELEGRAM_HANDLE] [r/REMARK] [g/Group]…`
+
+> ℹ️ **Information:**
+> * Edits the student at the specified `INDEX`. The index refers to the index number shown in the displayed student list. The index **must be a positive integer** 1, 2, 3, …
+> * At least one of the optional fields must be provided.
+
+
+> 💡**Tips:**
+> * Existing values will be updated to the input values.
+> * When editing groups, the existing groups of the student will be removed i.e adding of groups is not cumulative.
+> * You can remove all the student’s groups by typing `g/` without specifying any groups after it.
+> * You can remove the remark of a student by typing `r/` without specifying any remark after it.
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `edit 1 n/John e/john01@example.com` Edits the name of the first student to `John` and email to `john01@example.com` respectively.
+* `edit 2 n/Betty tg/` Edits the name of the second student to be `Betty` and clears her Telegram handle.
-### Locating persons by name: `find`
+
-Finds persons whose names contain any of the given keywords.
+### Locating students by keyword: `find`
+Finds students whose details include any of the specified keywords.
+You can find a student even if the keywords **match only partially**.
Format: `find KEYWORD [MORE_KEYWORDS]`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+> ℹ️ **Information:**
+> * 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 student's name is searched.
+> * Students matching at least one keyword will be returned (i.e. `OR` search).
+ e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`.
+
+Examples:
+* `find John` returns students `john` and `John Doe`.
+* `find Jo` returns students `John Doe` and `Johan Louis` as shown in the image below.
+ ![result for 'find Jo'](images/findFeature.png)
+
+
+
+### Filtering students using group: `filter`
+
+Filters and lists students belonging to any of the given group name keyword.
+You can filter students only when the keywords **match fully**.
+
+Format: `filter KEYWORD [MORE_KEYWORDS]`
+
+> ℹ️ **Information:**
+> * The search is case-sensitive. e.g `tut04` will not match `TUT04`.
+> * The order of the keywords does not matter. e.g. `TUT04 LAB05` will match all students in `TUT04` and/or `LAB05`.
+> * Only student's group name is searched.
+> * The group name KEYWORD provided must be an existing group in TutorsContactsPro for students to be filtered accurately.
+> * When a non-existing group name is provided, TutorsContactsPro will display the status message '0 students listed!'.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `filter LAB10` returns only `Kendra Huetta` belonging to group `LAB10`.
+* `filter TUT04` returns `John Doe` and `Johan Louis` belonging to group `TUT04` as shown in the image below.
+
+ ![result for 'filter TU'](images/filterFeature.png)
+
+
+
+### Marking students attendance by group week number: `mark`
+
+Marks the student's attendance according to the group name and week number. This way, you can easily keep track of students' attendance details.
-### Deleting a person : `delete`
+Format: `mark INDEX g/GROUP_NAME w/WEEK a/ATTENDANCE`
-Deletes the specified person from the address book.
+> ⚠️️ **Warning:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`, the group should exsist in the student
+> * `WEEK` must be a valid week number, from 1 to 13.
+> * `ATTENDANCE` must be either `A` for absent or `P`for present (should not be 'a' or 'p').
+
+> ℹ️ **Information:**
+> * Marks the student at the specified INDEX. The index refers to the index number shown in the displayed student list. The index must be a positive integer 1, 2, 3, …
+> * All the fields should be provided, index, group, week, attendance.
+> * Resize the TutorsContactsPro window to view the full 13 weeks attendance table.
+
+> 💡**Tips:**
+> * You can simply remark by overwritting the attendance
+
+Examples:
+* `mark 1 g/TUT05 w/1 a/P` Marks the attendance of the first student in the list. Marks `TUT05` week 1 attendance as present.
+* `mark 2 g/LAB05 w/1 a/A` Marks the attendance of the second student in the list. Marks `LAB05` week 1 attendance as absent as shown in the image below.
+
+ ![result for 'mark 1'](images/markAttendance.png)
+
+
+
+### Deleting a student : `delete`
+
+Deletes your specified student from the current list.
Format: `delete INDEX`
-* 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, …
+> ℹ️ **Information:**
+> * Deletes the student at the specified `INDEX`.
+> * The index refers to the index number shown in the current displayed student list.
+> * The index **must be a positive integer** 1, 2, 3, …
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `find Betsy` followed by `delete 1` deletes the 1st student in the results of the `find` command.
+* `list` followed by `delete 6` deletes the 6th student in TutorsContactsPro. In the example below, John Doe, the 6th student is deleted.
+ ![result for 'delete 7'](images/deleteFeature.png)
-### Clearing all entries : `clear`
+
-Clears all entries from the address book.
+## Group Commands
-Format: `clear`
+This section contains commands for managing groups in TutorsContactsPro.
-### Exiting the program : `exit`
+### Adding a group: `addgroup`
-Exits the program.
+Adds a new group to TutorsContactsPro, so that you can keep track of the groups you currently teach.
+TutorsContactsPro will then automatically generate a dedicated tab for each new group added.
+
+format: `addgroup g/GROUP_NAME`
+
+> ⚠️️ **Warning:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`.
+
+> ℹ️ **Information:**
+> * Adds a group with a given group name.
+> * Group names are unique.
+
+
+Examples:
+* `addgroup g/LAB12` adds the group `LAB12` to TutorsContactsPro.
+* `addgroup g/TUT01` adds the group `TUT01` to TutorsContactsPro as shown in the image below.
+ ![result for 'addgroup g/TUT01'](images/addgroupFeature.png)
+
+
+
+### Editing a group: `editgroup`
+
+Edits an existing group in TutorsContactsPro, allowing you to add or edit the Telegram invite link assigned for each group so that you can remain up to date with each group's information.
+
+Format: `editgroup g/GROUP_NAME tg/TELEGRAM_LINK`
+
+> ⚠️️ **Warning:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`.
+> * The Telegram invite link must be a valid Telegram group invite link or an empty string.
+
+> ℹ️ **Information:**
+> * Assigns the Telegram group invite link provided in `TELEGRAM_LINK` to the group with the given `GROUP_NAME`.
+
+Examples
+* `editgroup g/LAB12 tg/https://t.me/abcdefg` adds the specified Telegram link `https://t.me/abcdefg` to group `LAB12`.
+* `editgroup g/TUT01 tg/` removes the Telegram link from group `TUT01`.
+* `editgroup g/TUT01 tg/https://t.me/aafhkbwiu` adds the specified Telegram link `/https://t.me/aafhkbwiu` to group `TUT01` as shown in the image below.
+ ![result for 'editgroup g/TUT01 tg/https://t.me/aafhkbwiu'](images/editgroupFeature.png)
+
+
+
+### Deleting a group: `deletegroup`
+
+Deletes an existing group in TutorsContactsPro, so that you can start a fresh for the next teaching semester.
+TutorsContactsPro will then remove the dedicated tab for the group deleted.
+
+Format: `deletegroup g/GROUP_NAME`
+
+> ⚠️️ **Warning:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`.
+
+Examples:
+* `deletegroup g/LAB12` deletes the group `LAB12` from TutorsContactsPro.
+* `deletegroup g/TUT01` deletes the group `TUT01` from TutorsContactsPro as shown in the image below.
+ ![result for 'deletegroup g/TUT01'](images/deletegroupFeature.png)
+
+
+
+### Generating email template : `mail`
+
+Generates a prefilled email template containing the email addresses of students based on specific `GROUP NAME` entered.
+
+A pop-up window will be generated, allowing you to open the email template with a simple 'click' for greater convenience.
+
+Format: `mail [GROUP_NAME]`
+
+> ℹ️ **Information:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`.
+> * `GROUP_NAME` must be a valid existing group.
+> * It requires a **full match** for the keyword.
+> * `GROUP_NAME` is optional and when no `GROUP_NAME` is entered, email addresses of all students on the current list will be included.
+> * If `GROUP_NAME` is entered, only email addresses of students belonging to the `GROUP_NAME` specified will be included.
+
+Examples:
+* `mail` generates a email template containing email addresses of all students on the current list.
+* `mail LAB05` generates a email template containing email addresses of all students belonging to `LAB05` as shown in the image below.
+
+![result for 'mail LAB05'](images/MailFeature.png)
+
+The image below shows the email template generated.
+![result for 'mail LAB05'](images/mail_template.png)
+
+
+
+### Generating email template for Telegram link: `mailtg`
+
+Generates a prefilled email template, including the Telegram link for that specific group and email addresses of student recipients from the specified group.
+
+A pop-up window will be generated, allowing you to open the email template with a simple 'click' for greater convenience.
+
+Format: `mailtg g/GROUP_NAME`
+
+> ℹ️ **Information:**
+> * `GROUP_NAME` must be in the correct format of `TUT/REC/LAB` + `2-digit number`.
+> * `GROUP_NAME` must be a valid existing group.
+> * It requires a **full match** for the keyword.
+> * With contrast to the previous `mail` command, `mail` command catered for multiple groups whereas `mailtg` command is catered to generating an email template with respect to a specific group.
+
+Examples:
+* `mailtg g/TUT04` generates a email template containing the Telegram link for group `TUT04` and with pre-filled email addresses of all students belonging to `TUT04`.
+* `mailtg g/LAB05` generates a email template containing the Telegram link for group `LAB05` and with pre-filled email addresses of all students belonging to `LAB05` as shown in the images below.
+
+![result for 'mailtg LAB05'](images/MailtgFeature.png)
+
+The image below shows the email template containing the group Telegram link generated.
+![result for 'mailtg LAB05'](images/mailtgteleFeature.png)
-Format: `exit`
### Saving the data
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+TutorsContactsPro data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
### Editing the data file
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+TutorsContactsPro data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
-
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-
+[//]: # ()
+
+> **⚠️ Caution:**
+If your changes to the data file makes its format invalid, TutorsContactsPro will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the TutorsContactsPro to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+
### Archiving data files `[coming in v2.0]`
_Details coming soon ..._
+[Table Of Contents](#table-of-contents)
+
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## FAQ
+
+**Q**: How do I transfer my data to another computer?
+**A**: Install TutorsContactsPros in the other computer and overwrite the empty data file it creates with the file that contains the data of your .json file.
+
+**Q**: How do I save my data?
+**A**: Our TutorsContactsPros ensures the data is saved automatically without any additional functions.
+
+**Q**: I cannot see my attendance boxes, how can I fix this?
+**A**: Try maximizing the screen size to see all the components in the app.
+
+**Q**: Is there a mobile version of TutorsContactsPros available?
+**A**: Currently, TutorsContactsPros is only available for desktop use.
+
+**Q**: How do I update TutorsContactsPros to the latest version?
+**A**: To update TutorsContactsPros, visit our github website and download the latest version. Uninstall the previous version from your computer before installing the new version.
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**Q**: What should I do if TutorsContactsPros crashes?
+**A**: If TutorsContactsPros crashes, restart the application. If the problem persists, reinstall the software and ensure your computer meets the system requirements.
+
+[Table Of Contents](#table-of-contents)
--------------------------------------------------------------------------------------------------------------------
-## Known issues
+## Known issues
1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+[Table Of Contents](#table-of-contents)
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Planned Enhancements
+Given below are the planned future enhancements for the application.
+
+1. **Improve add feature**: Currently, TutorContactsPro allows the addition of students with the same exact details and names but different case for names which can be misleading. This can result in addition of duplicate students instead. Hence, we plan to improve the `add` feature to detect whether students added have completely similar details regardless of casing and throw a duplicate student added warning where appropriate.
+
+2. **Allow the display of Telegram invite link**: TutorsContactsPro currently only allows the assignment of the Telegram invite link to a specific group via the `editgroup` command which is not too informative. We plan to improve the interface to include a field to display the Telegram invite link assigned for each specific group.
+
+3. **Improve the group tabs**: After marking the attendance in a particular group tab, TutorsContactsPro currently will automatically direct users to the results tab which can result in inconvenience for users. We plan to improve the group tabs interface such that users will still be able to remain on their selected group tab aftering executing any commands.
+
+4. **Improve add Telegram link feature**: Currently, TutorsContactsPro only allows the assignment of a Telegram invite link to a specific group via the `editgroup` command which may not be very intuitive for users. We plan to improve this feature by combining it with the `addgroup` feature to allow addition of a group together with the Telegram invite link.
+
+5. **Add unmark attendance feature**: TutorsContactsPro currently allows TA users to mark the attendance for a particular student which can be inconvenient when users make a mistake marking the attendance and they are unable to undo it. Therefore, in the future, we plan to include an additional unmark attendance feature for a specified student.
+
+6. **Improve generation of email template command**: The current `mail` command only takes in the `GROUP_NAME` input with no prefixes whereas the `mailtg` command takes in a `g/` prefix with a `GROUP_NAME` input. The difference in the command format for these two closely related commands can make it complex for users. Therefore, we plan to simplify and standardise the command input for `mail` and `mailtg` to be `g/GROUP_NAME`.
+
+7. **Improve generation of email template command**: When executing mail, mailtg command with an invalid group, including an non-existing group, empty group and group without Telegram invite link. TutorsContactsPro currently does not display a warning for users. We plan to improve them such that a warning message will be displayed when the input provided is invalid.
+
+8. **Make filter feature error message more specific**: When filtered with a non-existing group, the current error message displays `0 students listed!` which is too general. We plan to use `filter` command to check whether the `GROUP_NAME` provided is an existing group. The error message will then also mention the reason for failing to filter any students: `Group does not exist, 0 students listed!`
+
+_coming soon ..._
+
+[Table Of Contents](#table-of-contents)
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Command summary
+
+| Action | Format, Examples |
+|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Help** | `help` |
+| **List** | `list` |
+| **Add** | `add n/NAME p/PHONE e/EMAIL y/YEAR m/MAJOR tg/TELEGRAM [r/REMARK] [g/GROUP_NAME]...` e.g., `add n/John Doe p/98765432 e/johnd@example.com y/2 m/Computer Science tg/johndoe r/Very quiet student g/TUT04 g/LAB10 ` |
+| **Edit** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [y/NUMBER] [m/MAJOR] [tg/TELEGRAM] [r/REMARK] [g/GROUP_NAME]` e.g., `edit 1 n/John e/john01@example.com` |
+| **Find** | `find KEYWORD [MORE_KEYWORDS]` e.g.,`find john tan` |
+| **Filter** | `filter KEYWORD [MORE_KEYWORDS]` e.g.,`filter TUT01` |
+| **Mark** | `mark INDEX g/GROUP_NAME w/WEEK a/ATTENDANCE` e.g., `mark 3 g/TUT02 w/1 a/A` |
+| **Delete** | `delete INDEX` e.g., `delete 1` |
+| **Addgroup** | `addgroup g/GROUP_NAME` |
+| **Editgroup** | `editgroup g/GROUP_NAME tg/TELEGRAM_LINK` |
+| **Deletegroup** | `deletegroup g/GROUP_NAME` |
+| **Mail** | `mail [GROUP_NAME]` e.g., `mail TUT01` |
+| **Mailtg** | `mailtg g/GROUP_NAME` e.g., `mailtg g/TUT01` |
+| **Clear** | `clear` |
+
+
+[Table Of Contents](#table-of-contents)
+
--------------------------------------------------------------------------------------------------------------------
+
+
+## Appendix A: Installing Java
+
+Follow these steps to install Java on your computer:
+
+1. **Download Java 11:**
+- Download Java 11 from [Oracle's website](https://www.oracle.com/sg/java/technologies/javase/jdk11-archive-downloads.html) based on your operating system.
+
+2. **Follow the instructions on Oracle's website**
+ - Go to the appropriate guide page and follow the instructions based on your operating system to complete the installation.
+ - [MacOS](https://docs.oracle.com/en/java/javase/11/install/installation-jdk-macos.html#GUID-2FE451B0-9572-4E38-A1A5-568B77B146DE)
+ - [Windows](https://docs.oracle.com/en/java/javase/11/install/installation-jdk-microsoft-windows-platforms.html#GUID-A7E27B90-A28D-4237-9383-A58B416071CA)
+ - [Linux](https://docs.oracle.com/en/java/javase/11/install/installation-jdk-linux-platforms.html#GUID-737A84E4-2EFF-4D38-8E60-3E29D1B884B8)
+
+3. **Verify Installation:**
+ After installation, open a terminal (on MacOS) or Command Prompt (on Windows) and run the following commands to verify that Java is installed:
+
+ ```
+ java -version
+ ```
+If installed correctly, you should see version information for both Java and the Java Compiler. The message should look something like this:
+```
+openjdk version "11.0.20" 2023-07-18 LTS
+OpenJDK Runtime Environment Zulu11.66+15-CA (build 11.0.20+8-LTS)
+OpenJDK 64-Bit Server VM Zulu11.66+15-CA (build 11.0.20+8-LTS, mixed mode)
+```
+
+Now, you're ready to proceed with the launching of TutorsContactsPro. Click [here](#feature-install) to get back to the installation tutorial.
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**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`
+[Table Of Contents](#table-of-contents)
diff --git a/docs/_config.yml b/docs/_config.yml
deleted file mode 100644
index 6bd245d8f4e..00000000000
--- a/docs/_config.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-title: "AB-3"
-theme: minima
-
-header_pages:
- - UserGuide.md
- - DeveloperGuide.md
- - AboutUs.md
-
-markdown: kramdown
-
-repository: "se-edu/addressbook-level3"
-github_icon: "images/github-icon.png"
-
-plugins:
- - jemoji
diff --git a/docs/_data/projects.yml b/docs/_data/projects.yml
deleted file mode 100644
index 8f3e50cb601..00000000000
--- a/docs/_data/projects.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-- name: "AB-1"
- url: https://se-edu.github.io/addressbook-level1
-
-- name: "AB-2"
- url: https://se-edu.github.io/addressbook-level2
-
-- name: "AB-3"
- url: https://se-edu.github.io/addressbook-level3
-
-- name: "AB-4"
- url: https://se-edu.github.io/addressbook-level4
-
-- name: "Duke"
- url: https://se-edu.github.io/duke
-
-- name: "Collate"
- url: https://se-edu.github.io/collate
-
-- name: "Book"
- url: https://se-edu.github.io/se-book
-
-- name: "Resources"
- url: https://se-edu.github.io/resources
diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html
deleted file mode 100644
index 8559a67ffad..00000000000
--- a/docs/_includes/custom-head.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% comment %}
- Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons:
-
- 1. Head over to https://realfavicongenerator.net/ to add your own favicons.
- 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet.
-{% endcomment %}
diff --git a/docs/_includes/head.html b/docs/_includes/head.html
deleted file mode 100644
index 83ac5326933..00000000000
--- a/docs/_includes/head.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
- {%- include custom-head.html -%}
-
- {{page.title}}
-
-
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
deleted file mode 100644
index 33badcd4f99..00000000000
--- a/docs/_includes/header.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
+
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
+Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
-
+
If you are stuck, check out the sample
[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a).
@@ -227,7 +230,7 @@ Now that we have all the information that we need, let’s lay the groundwork fo
### Add a new `Remark` class
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
+Create a new `Remark` in `seedu.major.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input
validation.
@@ -240,11 +243,11 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark`
Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
+Simply add the following to [`seedu.major.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
**`PersonCard.java`:**
-``` java
+```java
@FXML
private Label remark;
```
@@ -276,11 +279,11 @@ We change the constructor of `Person` to take a `Remark`. We will also need to d
Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
-
+
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
+Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
+
Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
@@ -291,11 +294,11 @@ AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the
While the changes to code may be minimal, the test data will have to be updated as well.
-
+
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
+You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty major book!
-
+
Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
to see what the changes entail.
@@ -308,7 +311,7 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c
**`PersonCard.java`:**
-``` java
+```java
public PersonCard(Person person, int displayedIndex) {
//...
remark.setText(person.getRemark().value);
@@ -328,7 +331,7 @@ save it with `Model#setPerson()`.
**`RemarkCommand.java`:**
-``` java
+```java
//...
public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s";
public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s";
@@ -344,7 +347,7 @@ save it with `Model#setPerson()`.
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = new Person(
personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
- personToEdit.getAddress(), remark, personToEdit.getTags());
+ personToEdit.getAddress(), remark, personToEdit.getGroups());
model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..5cbd6428451 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -1,26 +1,29 @@
---
-layout: page
-title: "Tutorial: Removing Fields"
+ layout: default.md
+ title: "Tutorial: Removing Fields"
+ pageNav: 3
---
+# Tutorial: Removing Fields
+
> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
>
> — Antoine de Saint-Exupery
When working on an existing code base, you will most likely find that some features that are no longer necessary.
-This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class.
+This tutorial aims to give you some practice on such a code 'removal' activity by removing the `major` field from `Person` class.
-
+
-**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.
+**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `major` field can be done similarly.
However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.**
-
+
-* Table of Contents
-{:toc}
+
+
## Safely deleting `Address`
@@ -28,7 +31,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+The `major` field in `Person` is actually an instance of the `seedu.address.model.person.Majorclass. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
@@ -41,19 +44,19 @@ Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., doub
Let’s try removing references to `Address` in `EditPersonDescriptor`.
-1. Safe delete the field `address` in `EditPersonDescriptor`.
+1. Safe delete the field `major` in `EditPersonDescriptor`.
1. Select `Yes` when prompted to remove getters and setters.
1. Select `View Usages` again.
![UnsafeDeleteOnField](../images/remove/UnsafeDeleteOnField.png)
-1. Remove the usages of `address` and select `Do refactor` when you are done.
+1. Remove the usages of `major` and select `Do refactor` when you are done.
-
+
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
-
+ **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `major` field from the `Person` class will require you to modify its constructor.
+
1. Repeat the steps for the remaining usages of `Address`
@@ -61,20 +64,20 @@ After you are done, verify that the application still works by compiling and run
### Manual refactoring
-Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`).
+Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `major` in your code (`Edit` \> `Find` \> `Find in path`).
-Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
+Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$major` in each `PersonCard` that has not been removed nor identified.
-![$address](../images/remove/$address.png)
+![$major](../images/remove/$major.png)
A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
**`PersonCard.java`**
-``` java
+```java
...
@FXML
-private Label address;
+private Label major;
...
```
@@ -83,7 +86,7 @@ private Label address;
``` xml
...
-
+
...
```
@@ -94,7 +97,7 @@ After removing the `Label`, we can proceed to formally test our code. If everyth
At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up references to `Address` in test data and documentation.
-In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
+In `src/test/data/`, data meant for testing purposes are stored. While keeping the `major` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
**`invalidPersonAddressBook.json`:**
@@ -104,9 +107,9 @@ In `src/test/data/`, data meant for testing purposes are stored. While keeping t
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "major": "4th street"
} ]
}
```
-You can go through each individual `json` file and manually remove the `address` field.
+You can go through each individual `json` file and manually remove the `major` field.
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..90ffaa4c97b 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -1,26 +1,30 @@
---
-layout: page
-title: "Tutorial: Tracing code"
+ layout: default.md
+ title: "Tutorial: Tracing code"
+ pageNav: 3
---
+# Tutorial: Tracing code
+
+
> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
>
> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-* Table of Contents
-{:toc}
+
+
## Before we start
Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-![ArchitectureDiagram](../images/ArchitectureDiagram.png)
+
It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
+
Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works.
@@ -37,18 +41,18 @@ As you know, the first step of debugging is to put in a breakpoint where you wan
In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component.
-
+
-According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
+According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.major.logic.Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-
+**Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
+
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
+A quick look at the `seedu.major.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
```java
public interface Logic {
@@ -67,14 +71,14 @@ public interface Logic {
But apparently, this is an interface, not a concrete implementation.
That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram:
-
+
Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
-
+**Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
+
![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png)
@@ -85,12 +89,12 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres
## Tracing the execution path
-Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
+Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/Group]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
-
+
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
-
+**Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
+
1. To start the debugging session, simply `Run` \> `Debug Main`
@@ -110,7 +114,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
**LogicManager\#execute().**
- ``` java
+ ```java
@Override
public CommandResult execute(String commandText)
throws CommandException, ParseException {
@@ -142,7 +146,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
![StepOver](../images/tracing/StepOver.png)
1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below):
- ``` java
+ ```java
public Command parseCommand(String userInput) throws ParseException {
...
final String commandWord = matcher.group("commandWord");
@@ -157,7 +161,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command).
- ``` java
+ ```java
...
case EditCommand.COMMAND_WORD:
return new EditCommandParser().parse(arguments);
@@ -166,8 +170,10 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`.
-
:bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
-
+
+
+ **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
+
1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
@@ -175,17 +181,17 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
![EditCommand](../images/tracing/EditCommand.png)
1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component.
-
+
1. Let’s continue stepping through until we return to `LogicManager#execute()`.
The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png)
+
1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below):
**`EditCommand#execute()`:**
- ``` java
+ ```java
@Override
public CommandResult execute(Model model) throws CommandException {
...
@@ -205,25 +211,28 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
* it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked.
-
+
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component)
1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component:
-
+
+
* :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component)
1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component.
-
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
-
+
+
+ **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
+
-1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
+1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
**`JsonSerializableAddressBook` constructor:**
- ``` java
+ ```java
/**
* Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
*
@@ -243,7 +252,8 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format.
1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
-
+
+
* :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component)
1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint).
@@ -251,7 +261,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in:
**`ResultDisplay#setFeedbackToUser()`**
- ``` java
+ ```java
public void setFeedbackToUser(String feedbackToUser) {
requireNonNull(feedbackToUser);
resultDisplay.setText(feedbackToUser);
@@ -292,10 +302,10 @@ Here are some quick questions you can try to answer based on your execution path
2. Allow `delete` to remove more than one index at a time
- 3. Save the address book in the CSV format instead
+ 3. Save the major book in the CSV format instead
4. Add a new command
5. Add a new field to `Person`
- 6. Add a new entity to the address book
+ 6. Add a new entity to the major book
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..ec439ad9ef9 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -36,7 +36,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 4, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..befc66ca2e6 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -13,29 +13,31 @@
public class StringUtil {
/**
- * Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
+ * Returns true if the {@code sentence} contains the {@code word} at the beginning of any word.
+ * Ignores case, a partial word match is allowed
* examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
- * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+ * containsWordIgnoreCase("ABc def", "AB") == true //partial word match
+ * containsWordIgnoreCase("ABc def", "Bc") == false //contains but not at the beginning of any word
*
* @param sentence cannot be null
* @param word cannot be null, cannot be empty, must be a single word
*/
- public static boolean containsWordIgnoreCase(String sentence, String word) {
+ public static boolean containsStartingWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
requireNonNull(word);
- String preppedWord = word.trim();
+ String preppedWord = word.trim().toLowerCase();
checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
- checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word");
+ checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a "
+ + "single word");
- String preppedSentence = sentence;
+ String preppedSentence = sentence.toLowerCase();
String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ .anyMatch(currWord -> currWord.startsWith(preppedWord));
}
/**
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..c86a3ec8a41 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -14,8 +14,8 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
+ public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The student index provided is invalid";
+ public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d students listed!";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
@@ -41,10 +41,16 @@ public static String format(Person person) {
.append(person.getPhone())
.append("; Email: ")
.append(person.getEmail())
- .append("; Address: ")
- .append(person.getAddress())
- .append("; Tags: ");
- person.getTags().forEach(builder::append);
+ .append("; Year: ")
+ .append(person.getYear())
+ .append("; Major: ")
+ .append(person.getMajor())
+ .append("; Telegram: ")
+ .append(person.getTelegram())
+ .append("; Remark: ")
+ .append(person.getRemark())
+ .append("; Groups: ");
+ person.getGroups().forEach(builder::append);
return builder.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..2d4a3ebb130 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,16 +1,20 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -25,15 +29,21 @@ public class AddCommand extends Command {
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + PREFIX_YEAR + "YEAR "
+ + PREFIX_MAJOR + "MAJOR "
+ + PREFIX_TELEGRAM + "TELEGRAM "
+ + "[" + PREFIX_REMARK + "REMARK] "
+ + "[" + PREFIX_GROUP + "Group]...\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_YEAR + "2 "
+ + PREFIX_MAJOR + "Computer Science "
+ + PREFIX_TELEGRAM + "johndoe "
+ + PREFIX_REMARK + "Very quiet student "
+ + PREFIX_GROUP + "TUT04 "
+ + PREFIX_GROUP + "LAB10 ";
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";
@@ -56,6 +66,12 @@ public CommandResult execute(Model model) throws CommandException {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ for (Group group : toAdd.getGroups()) {
+ if (!model.hasGroup(group)) {
+ throw new CommandException(String.format(Group.MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK, group));
+ }
+ }
+
model.addPerson(toAdd);
return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
}
diff --git a/src/main/java/seedu/address/logic/commands/AddGroupCommand.java b/src/main/java/seedu/address/logic/commands/AddGroupCommand.java
new file mode 100644
index 00000000000..4c6971eafc8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddGroupCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+
+/**
+ * Adds a group to the address book.
+ */
+public class AddGroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "addgroup";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a group to the address book. "
+ + "Parameters: "
+ + PREFIX_GROUP + "GROUP NAME ";
+
+ public static final String MESSAGE_SUCCESS = "New group added: %1$s";
+ public static final String MESSAGE_DUPLICATE_GROUP = "This group already exists in the address book";
+
+ private final Group toAdd;
+
+ /**
+ * Creates an AddGroupCommand
+ */
+ public AddGroupCommand(Group group) {
+ requireNonNull(group);
+ toAdd = group;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasGroup(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_GROUP);
+ }
+
+ model.addGroup(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddGroupCommand)) {
+ return false;
+ }
+
+ AddGroupCommand otherAddGroupCommand = (AddGroupCommand) other;
+ return toAdd.equals(otherAddGroupCommand.toAdd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toAdd", toAdd)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 249b6072d0d..304511e5696 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -12,9 +12,11 @@
public class CommandResult {
private final String feedbackToUser;
+ private final String mailtoLink;
/** Help information should be shown to the user. */
private final boolean showHelp;
+ private final boolean showMail;
/** The application should exit. */
private final boolean exit;
@@ -22,10 +24,23 @@ public class CommandResult {
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean showMail, boolean exit) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
+ this.showMail = showMail;
this.exit = exit;
+ mailtoLink = "";
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified fields.
+ */
+ public CommandResult(String feedbackToUser, String mailtoLink) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = false;
+ this.showMail = true;
+ this.exit = false;
+ this.mailtoLink = mailtoLink;
}
/**
@@ -33,16 +48,23 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false);
}
public String getFeedbackToUser() {
return feedbackToUser;
}
+ public String getMailtoLink() {
+ return this.mailtoLink;
+ }
+
public boolean isShowHelp() {
return showHelp;
}
+ public boolean isShowMail() {
+ return showMail;
+ }
public boolean isExit() {
return exit;
@@ -62,12 +84,13 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
+ && showMail == otherCommandResult.showMail
&& exit == otherCommandResult.exit;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, showMail, exit);
}
@Override
@@ -75,6 +98,7 @@ public String toString() {
return new ToStringBuilder(this)
.add("feedbackToUser", feedbackToUser)
.add("showHelp", showHelp)
+ .add("showMail", showMail)
.add("exit", exit)
.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..b30c68f686a 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -19,11 +19,11 @@ public class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
+ + ": Deletes the student identified by the index number used in the displayed person list.\n"
+ "Parameters: INDEX (must be a positive integer)\n"
+ "Example: " + COMMAND_WORD + " 1";
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Student: %1$s";
private final Index targetIndex;
diff --git a/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java
new file mode 100644
index 00000000000..f62c85bbd53
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java
@@ -0,0 +1,86 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * Deletes a group from the address book.
+ */
+public class DeleteGroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "deletegroup";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a group to the address book. "
+ + "Parameters: "
+ + PREFIX_GROUP + "GROUP NAME ";
+
+ public static final String MESSAGE_SUCCESS = "Group removed: %1$s";
+ public static final String MESSAGE_NOT_FOUND = "Group is not found";
+
+ private final Group toRemove;
+
+ /**
+ * Creates a DeleteGroupCommand
+ */
+ public DeleteGroupCommand(Group group) {
+ requireNonNull(group);
+ toRemove = group;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasGroup(toRemove)) {
+ throw new CommandException(MESSAGE_NOT_FOUND);
+ }
+
+ ObservableList persons = model.getAddressBook().getPersonList();
+ for (Person person : persons) {
+ if (person.getGroups().contains(toRemove)) {
+ Set editedGroups = new HashSet<>(person.getGroups());
+ editedGroups.remove(toRemove);
+ Person editedPerson = new Person(person.getName(), person.getPhone(), person.getEmail(),
+ person.getYear(), person.getTelegram(), person.getMajor(), person.getRemark(),
+ editedGroups);
+ model.setPerson(person, editedPerson);
+ }
+ }
+
+ model.deleteGroup(toRemove);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toRemove));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteGroupCommand)) {
+ return false;
+ }
+
+ DeleteGroupCommand otherAddGroupCommand = (DeleteGroupCommand) other;
+ return toRemove.equals(otherAddGroupCommand.toRemove);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toRemove", toRemove)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..4df94a5ef60 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,11 +1,14 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import java.util.Collections;
@@ -21,12 +24,15 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* Edits the details of an existing person in the address book.
@@ -42,8 +48,11 @@ public class EditCommand extends Command {
+ "[" + PREFIX_NAME + "NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_YEAR + "YEAR] "
+ + "[" + PREFIX_MAJOR + "MAJOR] "
+ + "[" + PREFIX_TELEGRAM + "TELEGRAM]"
+ + "[" + PREFIX_REMARK + "REMARK]"
+ + "[" + PREFIX_GROUP + "Group]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
@@ -79,6 +88,12 @@ public CommandResult execute(Model model) throws CommandException {
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+ for (Group group : editPersonDescriptor.getGroups().orElse(personToEdit.getGroups())) {
+ if (!model.hasGroup(group)) {
+ throw new CommandException(String.format(Group.MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK, group));
+ }
+ }
+
if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
@@ -98,10 +113,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ Year updatedYear = editPersonDescriptor.getYear().orElse(personToEdit.getYear());
+ Major updatedMajor = editPersonDescriptor.getMajor().orElse(personToEdit.getMajor());
+ Telegram updatedTelegram = editPersonDescriptor.getTelegram().orElse(personToEdit.getTelegram());
+ Remark updatedRemark = editPersonDescriptor.getRemark().orElse(personToEdit.getRemark());
+ Set updatedGroups = editPersonDescriptor.getGroups().orElse(personToEdit.getGroups());
+
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedYear, updatedTelegram,
+ updatedMajor, updatedRemark, updatedGroups);
}
@Override
@@ -136,28 +155,34 @@ public static class EditPersonDescriptor {
private Name name;
private Phone phone;
private Email email;
- private Address address;
- private Set tags;
+ private Year year;
+ private Major major;
+ private Telegram telegram;
+ private Remark remark;
+ private Set groups;
public EditPersonDescriptor() {}
/**
* Copy constructor.
- * A defensive copy of {@code tags} is used internally.
+ * A defensive copy of {@code groups} is used internally.
*/
public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
setPhone(toCopy.phone);
setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
+ setYear(toCopy.year);
+ setMajor(toCopy.major);
+ setTelegram(toCopy.telegram);
+ setRemark(toCopy.remark);
+ setGroups(toCopy.groups);
}
/**
* 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, year, major, telegram, remark, groups);
}
public void setName(Name name) {
@@ -184,29 +209,50 @@ public Optional getEmail() {
return Optional.ofNullable(email);
}
- public void setAddress(Address address) {
- this.address = address;
+ public void setYear(Year year) {
+ this.year = year;
+ }
+
+ public Optional getYear() {
+ return Optional.ofNullable(year);
+ }
+ public void setTelegram(Telegram telegram) {
+ this.telegram = telegram;
+ }
+ public Optional getTelegram() {
+ return Optional.ofNullable(telegram);
+ }
+
+ public void setMajor(Major major) {
+ this.major = major;
+ }
+
+ public Optional getMajor() {
+ return Optional.ofNullable(major);
+ }
+ public void setRemark(Remark remark) {
+ this.remark = remark;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getRemark() {
+ return Optional.ofNullable(remark);
}
/**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
+ * Sets {@code groups} to this object's {@code groups}.
+ * A defensive copy of {@code groups} is used internally.
*/
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ public void setGroups(Set groups) {
+ this.groups = (groups != null) ? new HashSet<>(groups) : null;
}
/**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * Returns an unmodifiable group set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
+ * Returns {@code Optional#empty()} if {@code groups} is null.
*/
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ public Optional> getGroups() {
+ return (groups != null) ? Optional.of(Collections.unmodifiableSet(groups)) : Optional.empty();
}
@Override
@@ -224,8 +270,11 @@ public boolean equals(Object other) {
return Objects.equals(name, otherEditPersonDescriptor.name)
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
- && Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(year, otherEditPersonDescriptor.year)
+ && Objects.equals(telegram, otherEditPersonDescriptor.telegram)
+ && Objects.equals(major, otherEditPersonDescriptor.major)
+ && Objects.equals(remark, otherEditPersonDescriptor.remark)
+ && Objects.equals(groups, otherEditPersonDescriptor.groups);
}
@Override
@@ -234,8 +283,11 @@ public String toString() {
.add("name", name)
.add("phone", phone)
.add("email", email)
- .add("address", address)
- .add("tags", tags)
+ .add("year", year)
+ .add("major", major)
+ .add("telegram", telegram)
+ .add("remark", remark)
+ .add("groups", groups)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/EditGroupCommand.java b/src/main/java/seedu/address/logic/commands/EditGroupCommand.java
new file mode 100644
index 00000000000..8b4b56a51ee
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditGroupCommand.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+
+/**
+ * Edits a group in the address book.
+ */
+public class EditGroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "editgroup";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edit the Telegram group link "
+ + "Parameters: "
+ + PREFIX_GROUP + "GROUP NAME "
+ + PREFIX_TELEGRAM + "TELEGRAM INVITE LINK ";
+
+ public static final String MESSAGE_SUCCESS = "Group edited: %1$s";
+ public static final String MESSAGE_NOT_FOUND = "Group is not found";
+
+ private final Group toEdit;
+ private final String link;
+
+ /**
+ * Creates an EditGroupCommand
+ */
+ public EditGroupCommand(Group group, String link) {
+ requireNonNull(group);
+ this.toEdit = group;
+ this.link = link;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasGroup(toEdit)) {
+ throw new CommandException(MESSAGE_NOT_FOUND);
+ }
+
+ Group editedGroup = new Group(toEdit.groupName, this.link);
+
+ model.setGroup(toEdit, editedGroup);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toEdit));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditGroupCommand)) {
+ return false;
+ }
+
+ EditGroupCommand otherAddGroupCommand = (EditGroupCommand) other;
+ return toEdit.equals(otherAddGroupCommand.toEdit);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toEdit", toEdit)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..eb7560413f9 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -13,7 +13,7 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true);
}
}
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 00000000000..12537931093
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java
@@ -0,0 +1,63 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+
+
+/**
+ * filters and lists all students in address book whose attribute 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 + ": filters all students who are in group "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " LAB10 TUT04";
+
+ private final GroupContainsKeywordsPredicate predicate;
+
+ public FilterCommand(GroupContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.updateFilteredPersonList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FilterCommand)) {
+ return false;
+ }
+
+ FilterCommand otherFilterCommand = (FilterCommand) other;
+ return predicate.equals(otherFilterCommand.predicate);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("group", predicate)
+ .toString();
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..07d26e2a23c 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -16,6 +16,6 @@ public class HelpCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false);
}
}
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 00000000000..cbfcd1d5fd6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/MailCommand.java
@@ -0,0 +1,80 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+
+/**
+ * Directs users to the HTML website with email links to all the students in the current list.
+ */
+public class MailCommand extends Command {
+
+ public static final String COMMAND_WORD = "mail";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": generates mailto link to students from "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " LAB10 TUT04";
+
+ public static final String SHOW_MAILTO_LINK = "Showing the Email window";
+
+ private final GroupContainsKeywordsPredicate predicate;
+
+ /**
+ * Constructs a MailCommand with a predicate.
+ */
+ public MailCommand(GroupContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ /**
+ * Constructs a MailCommand without any predicate.
+ */
+ public MailCommand() {
+ this.predicate = null;
+ }
+
+ /**
+ * Generates a mailto link consisting of emails of students filtered accordingly
+ * Shows a pop-up window containing the mailto link
+ */
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ ReadOnlyAddressBook addressBook = model.getAddressBook();
+ List personList = addressBook.getPersonList().filtered(predicate);
+
+ // Extract email addresses of filtered students
+ List emailList = personList.stream()
+ .map(Person::getEmail)
+ .filter(email -> !email.value.isEmpty())
+ .map(email -> email.value)
+ .collect(Collectors.toList());
+
+ String mailtoLink = "mailto:" + String.join(";", emailList);
+
+ return new CommandResult(SHOW_MAILTO_LINK, mailtoLink);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof MailCommand)) {
+ return false;
+ }
+
+ MailCommand otherMailCommand = (MailCommand) other;
+ return predicate.equals(otherMailCommand.predicate);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/MailTelegramCommand.java b/src/main/java/seedu/address/logic/commands/MailTelegramCommand.java
new file mode 100644
index 00000000000..0d537b7db9c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/MailTelegramCommand.java
@@ -0,0 +1,110 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+
+/**
+ * Directs users to the HTML website with email links to all the students in the current list.
+ */
+public class MailTelegramCommand extends Command {
+
+ public static final String COMMAND_WORD = "mailtg";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": generates mailto link to students from "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " LAB10 TUT04";
+
+ public static final String SHOW_MAILTO_LINK = "Showing the email window";
+
+ private final GroupContainsKeywordsPredicate predicate;
+ private final Group group;
+
+ /**
+ * Constructs a MailCommand with a predicate.
+ */
+ public MailTelegramCommand(Group group) {
+ this.group = group;
+ this.predicate = new GroupContainsKeywordsPredicate(Arrays.asList(group.groupName));
+ }
+
+ /**
+ * Generates a mailto link consisting of emails of students filtered accordingly
+ * Shows a pop-up window containing the mailto link
+ */
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ ReadOnlyAddressBook addressBook = model.getAddressBook();
+ List personList = addressBook.getPersonList().filtered(predicate);
+ List groupList = addressBook.getGroupList();
+
+ // Extract email addresses of filtered students
+ List emailList = personList.stream()
+ .map(Person::getEmail)
+ .filter(email -> !email.value.isEmpty())
+ .map(email -> email.value)
+ .collect(Collectors.toList());
+
+ String telegramLink = "";
+ for (Group curr : groupList) {
+ if (curr.isSameGroup(this.group)) {
+ telegramLink = curr.telegramLink;
+ break;
+ }
+ }
+
+ String mailtoLink = createMailtoUrl(String.join(";", emailList),
+ String.format("Welcome to Group %s", group.groupName),
+ String.format("Greetings,\n\nWelcome to Group %s.\n\n", group.groupName)
+ + String.format("Please join this Telegram group: %s.\n\n", telegramLink)
+ + String.format("Sent from TutorsContactPro."));
+
+ return new CommandResult(SHOW_MAILTO_LINK, mailtoLink);
+ }
+
+ /**
+ * Generates a mailto link
+ * @param recipient The recipient of the email
+ * @param subject The subject of the email
+ * @param body The body of the email
+ * @return A mailto link
+ */
+ public String createMailtoUrl(String recipient, String subject, String body) {
+ try {
+ String uri = "mailto:" + recipient
+ + "?subject="
+ + URLEncoder.encode(subject, "UTF-8").replace("+", "%20")
+ + "&body=" + URLEncoder.encode(body, "UTF-8").replace("+", "%20");
+ return uri;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create mailto URL", e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof MailTelegramCommand)) {
+ return false;
+ }
+
+ MailTelegramCommand otherMailCommand = (MailTelegramCommand) other;
+ return group.equals(otherMailCommand.group);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/MarkAttendanceCommand.java b/src/main/java/seedu/address/logic/commands/MarkAttendanceCommand.java
new file mode 100644
index 00000000000..0faf79ba82c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/MarkAttendanceCommand.java
@@ -0,0 +1,109 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDANCE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_WEEK;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * Marks attendance for a specified person in a group.
+ */
+public class MarkAttendanceCommand extends Command {
+
+ public static final String COMMAND_WORD = "mark";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks attendance for a specified person in a group.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_GROUP + "GROUP_NAME "
+ + PREFIX_WEEK + "WEEK_NUMBER (week 1 to week 13)"
+ + PREFIX_ATTENDANCE + "ABSENT_OR_PRESENT (A for absent P for present)"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_GROUP + "TUT04 "
+ + PREFIX_WEEK + "3 "
+ + PREFIX_ATTENDANCE + "A ";
+
+ public static final String MESSAGE_SUCCESS = "Attendance marked";
+ public static final String MESSAGE_WEEK_NUMBER_INVALID = "Week number should be between 1 and 13 inclusively.";
+ public static final String MESSAGE_ATTENDANCE_INVALID = "Attendance format is wrong (A for absent P for present)";
+ public static final String MESSAGE_GROUP_NOT_FOUND = "Group is not found in the person";
+
+ private final Index index;
+ private final Group group;
+ private final int week;
+ private final String attendance;
+
+ /**
+ * Creates a MarkAttendanceCommand to mark attendance of {@code Person} of given index
+ */
+ public MarkAttendanceCommand(Index index, Group group, int week, String attendance) {
+ requireNonNull(index);
+ requireNonNull(group);
+ requireNonNull(week);
+ requireNonNull(attendance);
+
+ this.index = index;
+ this.group = group;
+ this.week = week;
+ this.attendance = attendance;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List lastShownList = model.getFilteredPersonList();
+
+ //check if the person index is valid
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+
+ //check if it is valid group
+ if (!personToEdit.hasGroup(group)) {
+ throw new CommandException(MESSAGE_GROUP_NOT_FOUND);
+ }
+
+ // Week number is 1-based, so decrement it to access the correct index in the list
+ if (week < 0 || week > 13) {
+ throw new CommandException(MESSAGE_WEEK_NUMBER_INVALID);
+ }
+
+ // Modify the attendance for the specified week
+ personToEdit.getMatchingGroup(group).markAttendance(week, attendance);
+
+ // Use model.setPerson(personToEdit, personToEdit) to update the Person object in the model
+ model.setPerson(personToEdit, personToEdit);
+
+ // Return a CommandResult indicating the success of the operation
+ return new CommandResult(String.format(MESSAGE_SUCCESS));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof MarkAttendanceCommand)) {
+ return false;
+ }
+
+ MarkAttendanceCommand otherMarkAttendanceCommand = (MarkAttendanceCommand) other;
+ return index.equals(otherMarkAttendanceCommand.index)
+ && group.equals(otherMarkAttendanceCommand.group)
+ && week == otherMarkAttendanceCommand.week
+ && attendance.equals(otherMarkAttendanceCommand.attendance);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..cc1aef01399 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,23 +1,29 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
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.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* Parses input arguments and creates a new AddCommand object
@@ -31,21 +37,27 @@ 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_YEAR,
+ PREFIX_TELEGRAM, PREFIX_MAJOR, PREFIX_REMARK, PREFIX_GROUP);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_MAJOR, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_YEAR,
+ PREFIX_TELEGRAM) || !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_YEAR, PREFIX_TELEGRAM,
+ PREFIX_MAJOR, PREFIX_REMARK);
+
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Year year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get());
+ Major major = ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get());
+ Telegram telegram = ParserUtil.parseTelegram(argMultimap.getValue(PREFIX_TELEGRAM).get());
+ Remark remark = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).orElse(""));
+ Set groupList = ParserUtil.parseGroups(argMultimap.getAllValues(PREFIX_GROUP));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, year, telegram, major, remark, groupList);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java
new file mode 100644
index 00000000000..425f99df0a9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddGroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class AddGroupCommandParser 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 AddGroupCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUP);
+
+ Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUP).get());
+
+ return new AddGroupCommand(group);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..3f9d0b7af96 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,14 +9,21 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddGroupCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteGroupCommand;
import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.EditGroupCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.MailCommand;
+import seedu.address.logic.commands.MailTelegramCommand;
+import seedu.address.logic.commands.MarkAttendanceCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -62,9 +69,21 @@ public Command parseCommand(String userInput) throws ParseException {
case DeleteCommand.COMMAND_WORD:
return new DeleteCommandParser().parse(arguments);
+ case AddGroupCommand.COMMAND_WORD:
+ return new AddGroupCommandParser().parse(arguments);
+
+ case DeleteGroupCommand.COMMAND_WORD:
+ return new DeleteGroupCommandParser().parse(arguments);
+
+ case EditGroupCommand.COMMAND_WORD:
+ return new EditGroupCommandParser().parse(arguments);
+
case ClearCommand.COMMAND_WORD:
return new ClearCommand();
+ case FilterCommand.COMMAND_WORD:
+ return new FilterCommandParser().parse(arguments);
+
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);
@@ -77,6 +96,15 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case MarkAttendanceCommand.COMMAND_WORD:
+ return new MarkAttendanceCommandParser().parse(arguments);
+
+ case MailCommand.COMMAND_WORD:
+ return new MailCommandParser().parse(arguments);
+
+ case MailTelegramCommand.COMMAND_WORD:
+ return new MailTelegramCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..8bc530d595e 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -9,7 +9,12 @@ 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/");
- public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_YEAR = new Prefix("y/");
+ public static final Prefix PREFIX_MAJOR = new Prefix("m/");
+ public static final Prefix PREFIX_TELEGRAM = new Prefix("tg/");
+ public static final Prefix PREFIX_REMARK = new Prefix("r/");
+ public static final Prefix PREFIX_GROUP = new Prefix("g/");
+ public static final Prefix PREFIX_WEEK = new Prefix("w/");
+ public static final Prefix PREFIX_ATTENDANCE = new Prefix("a/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java
new file mode 100644
index 00000000000..8b6e79c7aa9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.DeleteGroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class DeleteGroupCommandParser 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 DeleteGroupCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUP);
+
+ Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUP).get());
+
+ return new DeleteGroupCommand(group);
+ }
+
+ /**
+ * 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/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..1a1f6b90b6d 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,11 +2,14 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import java.util.Collection;
import java.util.Collections;
@@ -17,7 +20,8 @@
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Remark;
/**
* Parses input arguments and creates a new EditCommand object
@@ -31,18 +35,18 @@ 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);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_YEAR, PREFIX_TELEGRAM, PREFIX_MAJOR, PREFIX_REMARK, PREFIX_GROUP);
Index index;
-
try {
index = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_YEAR,
+ PREFIX_TELEGRAM, PREFIX_MAJOR, PREFIX_REMARK);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
@@ -55,10 +59,21 @@ 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()));
+ if (argMultimap.getValue(PREFIX_YEAR).isPresent()) {
+ editPersonDescriptor.setYear(ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_MAJOR).isPresent()) {
+ editPersonDescriptor.setMajor(ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get()));
}
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+
+ if (argMultimap.getValue(PREFIX_TELEGRAM).isPresent()) {
+ editPersonDescriptor.setTelegram(ParserUtil.parseTelegram(argMultimap.getValue(PREFIX_TELEGRAM).get()));
+ }
+
+ parseRemarkForEdit(argMultimap.getValue(PREFIX_REMARK)).ifPresent(editPersonDescriptor::setRemark);
+
+ parseGroupsForEdit(argMultimap.getAllValues(PREFIX_GROUP)).ifPresent(editPersonDescriptor::setGroups);
if (!editPersonDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
@@ -68,18 +83,32 @@ public EditCommand parse(String args) throws ParseException {
}
/**
- * 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.
+ * Parses {@code Collection groups} into a {@code Set} if {@code groups} is non-empty.
+ * If {@code groups} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero groups.
+ */
+ private Optional> parseGroupsForEdit(Collection groups) throws ParseException {
+ assert groups != null;
+
+ if (groups.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection groupSet = groups.size() == 1 && groups.contains("") ? Collections.emptySet() : groups;
+ return Optional.of(ParserUtil.parseGroups(groupSet));
+ }
+
+ /**
+ * Parses {@code Optional remark} into a {@code Optional}.
+ * If {@code remark} is empty, it will return an empty {@code Optional}.
+ * If {@code remark} is present, it will parse it into a {@code Remark} object.
*/
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
- assert tags != null;
+ private Optional parseRemarkForEdit(Optional remark) throws ParseException {
+ requireNonNull(remark);
- if (tags.isEmpty()) {
+ if (remark.isEmpty()) {
return Optional.empty();
}
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
+ return Optional.of(ParserUtil.parseRemark(remark.get()));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/EditGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/EditGroupCommandParser.java
new file mode 100644
index 00000000000..8d47bd32fbb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditGroupCommandParser.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.EditGroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class EditGroupCommandParser 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 EditGroupCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP, PREFIX_TELEGRAM);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_TELEGRAM) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGroupCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUP, PREFIX_TELEGRAM);
+
+ Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUP).get());
+ String link = ParserUtil.parseLink(argMultimap.getValue(PREFIX_TELEGRAM).get());
+
+ return new EditGroupCommand(group, link);
+ }
+
+ /**
+ * 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/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..4b0f02f2da7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+/**
+ * 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 a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format where parameter is empty
+ * @throws ParseException if the user input does not conform the expected group naming 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[] groupKeywords = trimmedArgs.split("\\s+");
+
+ for (String keyword : groupKeywords) {
+ if (!Group.isValidGroupName(keyword)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Group.MESSAGE_CONSTRAINTS_KEYWORD));
+ }
+ }
+
+ return new FilterCommand(new GroupContainsKeywordsPredicate(Arrays.asList(groupKeywords)));
+ }
+
+}
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 00000000000..7354ae3f772
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/MailCommandParser.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.MailCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FilterCommand object
+ */
+public class MailCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterCommand
+ * and returns a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format where parameter is empty
+ * @throws ParseException if the user input does not conform the expected group naming format
+ */
+ public MailCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ return new MailCommand();
+ }
+
+ String[] groupKeywords = trimmedArgs.split("\\s+");
+
+ for (String keyword : groupKeywords) {
+ if (!Group.isValidGroupName(keyword)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Group.MESSAGE_CONSTRAINTS_KEYWORD));
+ }
+ }
+ return new MailCommand(new GroupContainsKeywordsPredicate(Arrays.asList(groupKeywords)));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/MailTelegramCommandParser.java b/src/main/java/seedu/address/logic/parser/MailTelegramCommandParser.java
new file mode 100644
index 00000000000..c0090a51d79
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/MailTelegramCommandParser.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddGroupCommand;
+import seedu.address.logic.commands.MailTelegramCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+
+/**
+ * Parses input arguments and creates a new FilterCommand object
+ */
+public class MailTelegramCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterCommand
+ * and returns a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format where parameter is empty
+ * @throws ParseException if the user input does not conform the expected group naming format
+ */
+ public MailTelegramCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUP);
+ Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUP).get());
+ return new MailTelegramCommand(group);
+ }
+
+ /**
+ * 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/MarkAttendanceCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkAttendanceCommandParser.java
new file mode 100644
index 00000000000..8fd9c8a2400
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/MarkAttendanceCommandParser.java
@@ -0,0 +1,57 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDANCE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_WEEK;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.MarkAttendanceCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class MarkAttendanceCommandParser 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 MarkAttendanceCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP, PREFIX_WEEK, PREFIX_ATTENDANCE);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ MarkAttendanceCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_WEEK, PREFIX_ATTENDANCE)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ MarkAttendanceCommand.MESSAGE_USAGE));
+ }
+
+ Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUP).get());
+ Integer week = ParserUtil.parseWeek(argMultimap.getValue(PREFIX_WEEK).get());
+ String attendance = ParserUtil.parseAttendance(argMultimap.getValue(PREFIX_ATTENDANCE).get());
+
+ return new MarkAttendanceCommand(index, group, week, attendance);
+ }
+
+ /**
+ * 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/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..2e92062c5b8 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -9,11 +9,14 @@
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -66,18 +69,18 @@ public static Phone parsePhone(String phone) throws ParseException {
}
/**
- * Parses a {@code String address} into an {@code Address}.
+ * Parses a {@code String address} into an {@code Major}.
* 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_CONSTRAINTS);
+ public static Major parseMajor(String major) throws ParseException {
+ requireNonNull(major);
+ String trimmedMajor = major.trim();
+ if (!Major.isValidMajor(trimmedMajor)) {
+ throw new ParseException(Major.MESSAGE_CONSTRAINTS);
}
- return new Address(trimmedAddress);
+ return new Major(trimmedMajor);
}
/**
@@ -96,29 +99,113 @@ public static Email parseEmail(String email) throws ParseException {
}
/**
- * Parses a {@code String tag} into a {@code Tag}.
+ * Parses a {@code String year} into an {@code Year}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code tag} is invalid.
+ * @throws ParseException if the given {@code year} is invalid.
*/
- public static Tag parseTag(String tag) throws ParseException {
- requireNonNull(tag);
- String trimmedTag = tag.trim();
- if (!Tag.isValidTagName(trimmedTag)) {
- throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
+ public static Telegram parseTelegram(String telegram) throws ParseException {
+ requireNonNull(telegram);
+ String trimmedTelegram = telegram.trim();
+ if (!Telegram.isValidTelegram(trimmedTelegram)) {
+ throw new ParseException(Telegram.MESSAGE_CONSTRAINTS);
}
- return new Tag(trimmedTag);
+ return new Telegram(trimmedTelegram);
+ }
+ /**
+ * Parses a {@code String year} into an {@code Year}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code year} is invalid.
+ */
+ public static Year parseYear(String year) throws ParseException {
+ requireNonNull(year);
+ String trimmedYear = year.trim();
+ if (!Year.isValidYear(trimmedYear)) {
+ throw new ParseException(Year.MESSAGE_CONSTRAINTS);
+ }
+ return new Year(trimmedYear);
+ }
+
+ /**
+ * Parses a {@code String remark} into an {@code Remark}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code remark} is invalid.
+ */
+ public static Remark parseRemark(String remark) throws ParseException {
+ requireNonNull(remark);
+ String trimmedRemark = remark.trim();
+ return new Remark(trimmedRemark);
+ }
+
+ /**
+ * Parses a {@code String group} into a {@code Group}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code group} is invalid.
+ */
+ public static Group parseGroup(String group) throws ParseException {
+ requireNonNull(group);
+ String trimmedGroup = group.trim();
+ if (!Group.isValidGroupName(trimmedGroup)) {
+ throw new ParseException(Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ }
+ return new Group(trimmedGroup);
}
/**
- * Parses {@code Collection tags} into a {@code Set}.
+ * Parses {@code Collection groups} into a {@code Set}.
+ */
+ public static Set parseGroups(Collection groups) throws ParseException {
+ requireNonNull(groups);
+ final Set groupSet = new HashSet<>();
+ for (String groupName : groups) {
+ groupSet.add(parseGroup(groupName));
+ }
+ return groupSet;
+ }
+
+ /**
+ * Parses a {@code String link} into a {@code Group}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code link} is invalid.
+ */
+ public static String parseLink(String link) throws ParseException {
+ requireNonNull(link);
+ String trimmedGroup = link.trim();
+ if (!Group.isValidLink(trimmedGroup)) {
+ throw new ParseException(Group.MESSAGE_LINK_CONSTRAINTS);
+ }
+ return link;
+ }
+
+ /**
+ * Parses {@code String week} into an {@code Integer} and returns it. Leading and trailing whitespaces will be
+ * trimmed.
+ * @throws ParseException if the specified week is invalid.
+ */
+ public static Integer parseWeek(String week) throws ParseException {
+ String trimmedWeek = week.trim();
+ if (!Group.isValidWeek(trimmedWeek)) {
+ throw new ParseException(Group.WEEK_MESSAGE_CONSTRAINTS);
+ }
+ int weekInt = Integer.parseInt(trimmedWeek);
+ return weekInt;
+ }
+
+
+ /**
+ * Parses {@code String attendance} into an {@code String} and returns it. Leading and trailing whitespaces will be
+ * trimmed.
+ * @throws ParseException if the specified attendance is invalid.
*/
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ public static String parseAttendance(String attendance) throws ParseException {
+ String trimmedAttendance = attendance.trim();
+ if (!Group.isValidAttendance(trimmedAttendance)) {
+ throw new ParseException(Group.ATTENDANCE_MESSAGE_CONSTRAINTS);
}
- return tagSet;
+ return attendance;
}
}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java
index 348b7686c8a..965c7e445d4 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/seedu/address/logic/parser/Prefix.java
@@ -2,7 +2,7 @@
/**
* A prefix that marks the beginning of an argument in an arguments string.
- * E.g. 't/' in 'add James t/ friend'.
+ * E.g. 'g/' in 'add James g/ "TUT04"'.
*/
public class Prefix {
private final String prefix;
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..548516a1cd6 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -6,6 +6,8 @@
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.UniqueGroupList;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
@@ -16,6 +18,7 @@
public class AddressBook implements ReadOnlyAddressBook {
private final UniquePersonList persons;
+ private final UniqueGroupList groups;
/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
@@ -26,6 +29,7 @@ public class AddressBook implements ReadOnlyAddressBook {
*/
{
persons = new UniquePersonList();
+ groups = new UniqueGroupList();
}
public AddressBook() {}
@@ -48,6 +52,14 @@ public void setPersons(List persons) {
this.persons.setPersons(persons);
}
+ /**
+ * Replaces the contents of the group list with {@code groups}.
+ * {@code groups} must not contain duplicate groups.
+ */
+ public void setGroups(List groups) {
+ this.groups.setGroups(groups);
+ }
+
/**
* Resets the existing data of this {@code AddressBook} with {@code newData}.
*/
@@ -55,6 +67,7 @@ public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
setPersons(newData.getPersonList());
+ setGroups(newData.getGroupList());
}
//// person-level operations
@@ -67,6 +80,14 @@ public boolean hasPerson(Person person) {
return persons.contains(person);
}
+ /**
+ * Returns true if a group with the same identity as {@code group} exists in the address book.
+ */
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return groups.contains(group);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
@@ -75,6 +96,14 @@ public void addPerson(Person p) {
persons.add(p);
}
+ /**
+ * Adds a group to the address book.
+ * The group must not already exist in the address book.
+ */
+ public void addGroup(Group g) {
+ groups.add(g);
+ }
+
/**
* Replaces the given person {@code target} in the list with {@code editedPerson}.
* {@code target} must exist in the address book.
@@ -86,6 +115,17 @@ public void setPerson(Person target, Person editedPerson) {
persons.setPerson(target, editedPerson);
}
+ /**
+ * Replaces the given group {@code target} in the list with {@code editedGroup}.
+ * {@code target} must exist in the address book.
+ * The group identity of {@code editedGroup} must not be the same as another existing group in the address book.
+ */
+ public void setGroup(Group target, Group editedGroup) {
+ requireNonNull(editedGroup);
+
+ groups.setGroup(target, editedGroup);
+ }
+
/**
* Removes {@code key} from this {@code AddressBook}.
* {@code key} must exist in the address book.
@@ -94,12 +134,21 @@ public void removePerson(Person key) {
persons.remove(key);
}
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeGroup(Group key) {
+ groups.remove(key);
+ }
+
//// util methods
@Override
public String toString() {
return new ToStringBuilder(this)
.add("persons", persons)
+ .add("groups", groups)
.toString();
}
@@ -108,6 +157,11 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ @Override
+ public ObservableList getGroupList() {
+ return groups.asUnmodifiableObservableList();
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -120,11 +174,12 @@ public boolean equals(Object other) {
}
AddressBook otherAddressBook = (AddressBook) other;
- return persons.equals(otherAddressBook.persons);
+ return persons.equals(otherAddressBook.persons)
+ && groups.equals(otherAddressBook.groups);
}
@Override
public int hashCode() {
- return persons.hashCode();
+ return persons.hashCode() + groups.hashCode();
}
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..e3d3e13abfd 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,6 +5,7 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -84,4 +85,28 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Returns true if a group with the same name as {@code group} exists in the address book.
+ */
+ boolean hasGroup(Group group);
+
+ /**
+ * Deletes the given group.
+ * The group must exist in the address book.
+ */
+ void deleteGroup(Group target);
+
+ /**
+ * Adds the given group.
+ * {@code group} must not already exist in the address book.
+ */
+ void addGroup(Group group);
+
+ /**
+ * Replaces the given group {@code target} with {@code editedGroup}.
+ * {@code target} must exist in the address book.
+ * The group name of {@code editedGroup} must not be the same as another existing group in the address book.
+ */
+ void setGroup(Group target, Group editedGroup);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..6e2736e82f1 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,6 +11,7 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -111,6 +112,30 @@ public void setPerson(Person target, Person editedPerson) {
addressBook.setPerson(target, editedPerson);
}
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return addressBook.hasGroup(group);
+ }
+
+ @Override
+ public void deleteGroup(Group target) {
+ addressBook.removeGroup(target);
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ addressBook.addGroup(group);
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ }
+
+ @Override
+ public void setGroup(Group target, Group editedGroup) {
+ requireAllNonNull(target, editedGroup);
+
+ addressBook.setGroup(target, editedGroup);
+ }
+
//=========== 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 6ddc2cd9a29..d1bad685bf3 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.group.Group;
import seedu.address.model.person.Person;
/**
@@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook {
*/
ObservableList getPersonList();
+ /**
+ * Returns an unmodifiable view of the groups list.
+ * This list will not contain any duplicate groups.
+ */
+ ObservableList getGroupList();
+
}
diff --git a/src/main/java/seedu/address/model/group/Group.java b/src/main/java/seedu/address/model/group/Group.java
new file mode 100644
index 00000000000..c2667a4c1be
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/Group.java
@@ -0,0 +1,158 @@
+package seedu.address.model.group;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a Group in the address book.
+ * Guarantees: immutable; name is valid as declared in {@link #isValidGroupName(String)}
+ */
+public class Group {
+
+ public static final String MESSAGE_GROUP_NAME_CONSTRAINTS = "Groups names should be in correct format with"
+ + " 2 digit number. E.g. g/TUT04, g/LAB10, g/REC09. ";
+ public static final String MESSAGE_LINK_CONSTRAINTS = "Link should be a valid Telegram invite link.";
+ public static final String WEEK_MESSAGE_CONSTRAINTS = "Week number should be between 1 and 13 inclusively. ";
+ public static final String ATTENDANCE_MESSAGE_CONSTRAINTS = "Attendance should be A or P. ";
+ public static final String MESSAGE_CONSTRAINTS_KEYWORD = "Groups names should be in correct format "
+ + "with 2 digit number. "
+ + "E.g. TUT04, LAB10, REC09.";
+
+ public static final String MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK = "Group %s is not in the address book.";
+ public static final String GROUP_NAME_VALIDATION_REGEX = "^(TUT|LAB|REC)\\d{2}$";
+ public static final String LINK_VALIDATION_REGEX = "(https://t\\.me/[A-Za-z0-9_+-]+)?";
+ public static final String ATTENDANCE_VALIDATION_REGEX = "^[AP]$";
+ public static final String WEEK_VALIDATION_REGEX = "^[1-9]\\d?$";
+
+ public static final int MAX_NUM_OF_WEEKS = 13;
+
+ public final String groupName;
+ public final String telegramLink;
+ public final List attendance;
+
+ /**
+ * Constructs a {@code Group}.
+ *
+ * @param groupName A valid group name.
+ */
+ public Group(String groupName) {
+ requireNonNull(groupName);
+ checkArgument(isValidGroupName(groupName), MESSAGE_GROUP_NAME_CONSTRAINTS);
+ this.groupName = groupName;
+ this.telegramLink = "";
+ this.attendance = new ArrayList<>();
+ for (int i = 0; i < MAX_NUM_OF_WEEKS; i++) {
+ attendance.add("_");
+ }
+ }
+
+ /**
+ * Constructs a {@code Group}.
+ *
+ * @param groupName A valid group name.
+ * @param link A valid Telegram link.
+ */
+ public Group(String groupName, String link) {
+ requireNonNull(groupName);
+ checkArgument(isValidGroupName(groupName), MESSAGE_GROUP_NAME_CONSTRAINTS);
+ checkArgument(isValidLink(link), MESSAGE_LINK_CONSTRAINTS);
+ this.groupName = groupName;
+ this.telegramLink = link;
+ this.attendance = new ArrayList<>();
+ }
+
+ /**
+ * Constructs a {@code Group}.
+ *
+ * @param groupName A valid group name.
+ * @param attendance A list of String to represent attendance.
+ */
+ public Group(String groupName, List attendance) {
+ requireNonNull(groupName);
+ checkArgument(isValidGroupName(groupName), MESSAGE_GROUP_NAME_CONSTRAINTS);
+ this.groupName = groupName;
+ this.telegramLink = "";
+ this.attendance = new ArrayList<>(attendance);
+ }
+
+ /**
+ * Marks attendance of given week.
+ *
+ * @param week A valid week number.
+ * @param update A valid attendance.
+ */
+ public void markAttendance(Integer week, String update) {
+ requireNonNull(update);
+ this.attendance.set(week - 1, update);
+ }
+
+ /**
+ * Returns true if a given string is a valid group.
+ */
+ public static boolean isValidGroupName(String test) {
+ return test.matches(GROUP_NAME_VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns true if a given string is a valid link.
+ */
+ public static boolean isValidLink(String test) {
+ return test.matches(LINK_VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns true if a given string is a valid week.
+ */
+ public static boolean isValidWeek(String test) {
+ return test.matches(WEEK_VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns true if a given string is a valid attendance.
+ */
+ public static boolean isValidAttendance(String test) {
+ return test.matches(ATTENDANCE_VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Group)) {
+ return false;
+ }
+
+ Group otherGroup = (Group) other;
+ return groupName.equals(otherGroup.groupName);
+ }
+
+ /**
+ * Returns true if both groups have the same name.
+ * This defines a weaker notion of equality between two groups.
+ */
+ public boolean isSameGroup(Group otherGroup) {
+ if (otherGroup == this) {
+ return true;
+ }
+
+ return otherGroup != null && otherGroup.groupName.equals(this.groupName);
+ }
+
+ @Override
+ public int hashCode() {
+ return groupName.hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return '[' + groupName + ']';
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..e2ed12890a5
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java
@@ -0,0 +1,52 @@
+package seedu.address.model.group;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class GroupContainsKeywordsPredicate implements Predicate {
+
+ private final List groups;
+
+ /**
+ * Returns a GroupContainsKeywordsPredicate object by taking a list of the group names.
+ */
+ public GroupContainsKeywordsPredicate(List keywords) {
+ this.groups = keywords.stream().map(Group::new).collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean test(Person person) {
+ Set personGroup = person.getGroups();
+ return groups.stream()
+ .anyMatch(personGroup::contains);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof GroupContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ GroupContainsKeywordsPredicate otherGroupContainsKeywordsPredicate = (GroupContainsKeywordsPredicate) other;
+ return groups.equals(otherGroupContainsKeywordsPredicate.groups);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("groups", groups).toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/group/UniqueGroupList.java b/src/main/java/seedu/address/model/group/UniqueGroupList.java
new file mode 100644
index 00000000000..dad3e9c3c3d
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/UniqueGroupList.java
@@ -0,0 +1,150 @@
+package seedu.address.model.group;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.group.exceptions.DuplicateGroupException;
+import seedu.address.model.group.exceptions.GroupNotFoundException;
+
+/**
+ * A list of groups that enforces uniqueness between its elements and does not allow nulls.
+ * A group is considered unique by comparing using {@code Group#isSameGroup(Group)}. As such, adding and updating of
+ * groups uses Group#isSameGroup(Group) for equality so as to ensure that the group being added or updated is
+ * unique in terms of identity in the UniqueGroupList. However, the removal of a group uses Group#equals(Object) so
+ * as to ensure that the group with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Group#isSameGroup(Group)
+ */
+public class UniqueGroupList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent group as the given argument.
+ */
+ public boolean contains(Group toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameGroup);
+ }
+
+ /**
+ * Adds a group to the list.
+ * The group must not already exist in the list.
+ */
+ public void add(Group toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateGroupException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the group {@code target} in the list with {@code editedGroup}.
+ * {@code target} must exist in the list.
+ * The group identity of {@code editedGroup} must not be the same as another existing group in the list.
+ */
+ public void setGroup(Group target, Group editedGroup) {
+ requireAllNonNull(target, editedGroup);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new GroupNotFoundException();
+ }
+
+ if (!target.isSameGroup(editedGroup) && contains(editedGroup)) {
+ throw new DuplicateGroupException();
+ }
+
+ internalList.set(index, editedGroup);
+ }
+
+ /**
+ * Removes the equivalent group from the list.
+ * The group must exist in the list.
+ */
+ public void remove(Group toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new GroupNotFoundException();
+ }
+ }
+
+ public void setGroups(UniqueGroupList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code groups}.
+ * {@code groups} must not contain duplicate groups.
+ */
+ public void setGroups(List groups) {
+ requireAllNonNull(groups);
+ if (!groupsAreUnique(groups)) {
+ throw new DuplicateGroupException();
+ }
+
+ internalList.setAll(groups);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UniqueGroupList)) {
+ return false;
+ }
+
+ UniqueGroupList otherUniqueGroupList = (UniqueGroupList) other;
+ return internalList.equals(otherUniqueGroupList.internalList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return internalList.toString();
+ }
+
+ /**
+ * Returns true if {@code groups} contains only unique groups.
+ */
+ private boolean groupsAreUnique(List groups) {
+ for (int i = 0; i < groups.size() - 1; i++) {
+ for (int j = i + 1; j < groups.size(); j++) {
+ if (groups.get(i).isSameGroup(groups.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java
new file mode 100644
index 00000000000..d25f4c28bc4
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.group.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
+ * identity).
+ */
+public class DuplicateGroupException extends RuntimeException {
+ public DuplicateGroupException() {
+ super("Operation would result in duplicate groups");
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java
new file mode 100644
index 00000000000..8cae3bec756
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.group.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified person.
+ */
+public class GroupNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Major.java
similarity index 63%
rename from src/main/java/seedu/address/model/person/Address.java
rename to src/main/java/seedu/address/model/person/Major.java
index 469a2cc9a1e..45b6a18242a 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Major.java
@@ -5,11 +5,12 @@
/**
* Represents a Person's address in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
+ * Guarantees: immutable; is valid as declared in {@link #isValidMajor(String)}
*/
-public class Address {
+public class Major {
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
+ public static final String MESSAGE_CONSTRAINTS = "Major should only consist of Computing majors, "
+ + "and it should mot be blank";
/*
* The first character of the address must not be a whitespace,
@@ -20,20 +21,20 @@ public class Address {
public final String value;
/**
- * Constructs an {@code Address}.
+ * Constructs an {@code Major}.
*
- * @param address A valid address.
+ * @param major A valid address.
*/
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
+ public Major(String major) {
+ requireNonNull(major);
+ checkArgument(isValidMajor(major), MESSAGE_CONSTRAINTS);
+ value = major;
}
/**
* Returns true if a given string is a valid email.
*/
- public static boolean isValidAddress(String test) {
+ public static boolean isValidMajor(String test) {
return test.matches(VALIDATION_REGEX);
}
@@ -49,12 +50,12 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof Address)) {
+ if (!(other instanceof Major)) {
return false;
}
- Address otherAddress = (Address) other;
- return value.equals(otherAddress.value);
+ Major otherMajor = (Major) other;
+ return value.equals(otherMajor.value);
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..13e358a79bd 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -10,13 +10,13 @@
public class Name {
public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
+ "Names should only contain alphabets, periods, slashes, spaces, and it should not be blank";
/*
* The first character of the address must not be a whitespace,
* otherwise " " (a blank string) becomes a valid input.
*/
- public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}/.][\\p{Alnum}/. ]*";
public final String fullName;
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
index 62d19be2977..38d00c43e4d 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
@@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) {
@Override
public boolean test(Person person) {
return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ .anyMatch(keyword -> StringUtil.containsStartingWordIgnoreCase(person.getName().fullName, keyword));
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..c949082f871 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -8,10 +8,10 @@
import java.util.Set;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.group.Group;
/**
- * Represents a Person in the address book.
+ * Represents a Person in the major book.
* Guarantees: details are present and not null, field values are validated, immutable.
*/
public class Person {
@@ -20,21 +20,30 @@ public class Person {
private final Name name;
private final Phone phone;
private final Email email;
+ private final Year year;
+ private final Telegram telegram;
// Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
+ private final Major major;
+ private final Remark remark;
+ private final Set groups = 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, Year year, Telegram telegram, Major major, Remark remark,
+ Set groups) {
+ requireAllNonNull(name, phone, email, major, remark, groups);
+
this.name = name;
this.phone = phone;
this.email = email;
- this.address = address;
- this.tags.addAll(tags);
+ this.year = year;
+ this.major = major;
+ this.telegram = telegram;
+ this.remark = remark;
+ this.groups.addAll(groups);
}
public Name getName() {
@@ -49,16 +58,50 @@ public Email getEmail() {
return email;
}
- public Address getAddress() {
- return address;
+ public Major getMajor() {
+ return major;
+ }
+ public Year getYear() {
+ return year;
+ }
+ public Telegram getTelegram() {
+ return telegram;
+ }
+ public Remark getRemark() {
+ return remark;
}
/**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * Returns an immutable group set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
*/
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
+ public Set getGroups() {
+ return Collections.unmodifiableSet(groups);
+ }
+
+ /**
+ * Checks if the person belongs to a specific group.
+ *
+ * @param group The group to check.
+ * @return True if the person belongs to the group, false otherwise.
+ */
+ public boolean hasGroup(Group group) {
+ return groups.contains(group);
+ }
+
+ /**
+ * Returns the group from the person's set of groups that matches the given group.
+ *
+ * @param group The group to match.
+ * @return The matching group.
+ */
+ public Group getMatchingGroup(Group group) {
+ for (Group personGroup : groups) {
+ if (personGroup.isSameGroup(group)) {
+ return personGroup;
+ }
+ }
+ return null;
}
/**
@@ -93,14 +136,16 @@ public boolean equals(Object other) {
return name.equals(otherPerson.name)
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ && year.equals(otherPerson.year)
+ && telegram.equals(otherPerson.telegram)
+ && major.equals(otherPerson.major)
+ && remark.equals(otherPerson.remark);
}
@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, year, telegram, major, remark, groups);
}
@Override
@@ -109,8 +154,11 @@ public String toString() {
.add("name", name)
.add("phone", phone)
.add("email", email)
- .add("address", address)
- .add("tags", tags)
+ .add("year", year)
+ .add("telegram", telegram)
+ .add("major", major)
+ .add("remark", remark)
+ .add("groups", groups)
.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Remark.java b/src/main/java/seedu/address/model/person/Remark.java
new file mode 100644
index 00000000000..855c47bf6f2
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Remark.java
@@ -0,0 +1,46 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a remark for a Person in the address book.
+ * Guarantees: immutable; is always valid
+ */
+public class Remark {
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Remark}.
+ * If the given remark is null or empty, an empty string will be used.
+ *
+ * @param remark A valid remark, or null.
+ */
+ public Remark(String remark) {
+ requireNonNull(remark);
+ this.value = remark;
+ }
+ @Override
+ public String toString() {
+ return value;
+ }
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Remark)) {
+ return false;
+ }
+
+ Remark otherRemark = (Remark) other;
+ return value.equals(otherRemark.value);
+ }
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Telegram.java b/src/main/java/seedu/address/model/person/Telegram.java
new file mode 100644
index 00000000000..cec88176490
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Telegram.java
@@ -0,0 +1,62 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's Telegram handle in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidTelegram(String)}
+ */
+public class Telegram {
+
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Telegram handles should be alphanumeric and containing underscores, "
+ + "and must be at least 3 characters long.";
+ public static final String VALIDATION_REGEX = "^[a-zA-Z0-9_]{3,}$";
+ public final String value;
+
+ /**
+ * Constructs a {@code Handle}.
+ *
+ * @param handle A valid Telegram handle.
+ */
+ public Telegram(String handle) {
+ requireNonNull(handle);
+ checkArgument(isValidTelegram(handle), MESSAGE_CONSTRAINTS);
+ value = handle;
+ }
+
+ /**
+ * Returns true if a given string is a valid phone number.
+ */
+ public static boolean isValidTelegram(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Telegram)) {
+ return false;
+ }
+
+ Telegram otherTelegram = (Telegram) other;
+ return value.equals(otherTelegram.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Year.java b/src/main/java/seedu/address/model/person/Year.java
new file mode 100644
index 00000000000..005be43252a
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Year.java
@@ -0,0 +1,63 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's year in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidYear(String)}
+ */
+public class Year {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Year should only contain a single digit ranging from 1 - 5, and it should not be blank";
+
+ public static final String VALIDATION_REGEX = "[1-5]";
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Year}.
+ *
+ * @param value A valid year.
+ */
+ public Year(String value) {
+ requireNonNull(value);
+ checkArgument(isValidYear(value), MESSAGE_CONSTRAINTS);
+ this.value = value;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidYear(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Year)) {
+ return false;
+ }
+
+ Year otherYear = (Year) other;
+ return value.equals(otherYear.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
deleted file mode 100644
index f1a0d4e233b..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package seedu.address.model.tag;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Tag in the address book.
- * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
- */
-public class Tag {
-
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
-
- /**
- * Constructs a {@code Tag}.
- *
- * @param tagName A valid tag name.
- */
- public Tag(String tagName) {
- requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
- this.tagName = tagName;
- }
-
- /**
- * Returns true if a given string is a valid tag name.
- */
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Tag)) {
- return false;
- }
-
- Tag otherTag = (Tag) other;
- return tagName.equals(otherTag.tagName);
- }
-
- @Override
- public int hashCode() {
- return tagName.hashCode();
- }
-
- /**
- * Format state as text for viewing.
- */
- public String toString() {
- return '[' + tagName + ']';
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..493fb2facd2 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -6,12 +6,15 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -20,23 +23,42 @@ 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 Year("1"), new Telegram("alexyeoh1"),
+ new Major("Computer Science"),
+ new Remark("Very quiet"),
+ getGroupSet("TUT04")),
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 Year("1"), new Telegram("berniceyu123"),
+ new Major("Computer Science"),
+ new Remark("Outspoken"),
+ getGroupSet("TUT04", "LAB05")),
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 Year("2"), new Telegram("charlotte7"),
+ new Major("Computer Science"),
+ new Remark("Weak technical foundation"),
+ getGroupSet("TUT04")),
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 Year("4"), new Telegram("davidli456"),
+ new Major("Business Analytics"),
+ new Remark("Second time taking the course"),
+ getGroupSet("TUT04")),
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 Year("3"), new Telegram("irfan123"),
+ new Major("Computer Engineering"),
+ new Remark("Hardworking"),
+ getGroupSet("TUT04")),
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 Year("5"), new Telegram("roy5"),
+ new Major("Computer Engineering"),
+ new Remark("Always submits problem sets late"),
+ getGroupSet("LAB05"))
+ };
+ }
+
+ public static Group[] getSampleGroups() {
+ return new Group[] {
+ new Group("TUT04"),
+ new Group("LAB05")
};
}
@@ -45,15 +67,18 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
for (Person samplePerson : getSamplePersons()) {
sampleAb.addPerson(samplePerson);
}
+ for (Group sampleGroup : getSampleGroups()) {
+ sampleAb.addGroup(sampleGroup);
+ }
return sampleAb;
}
/**
- * Returns a tag set containing the list of strings given.
+ * Returns a group set containing the list of strings given.
*/
- public static Set getTagSet(String... strings) {
+ public static Set getGroupSet(String... strings) {
return Arrays.stream(strings)
- .map(Tag::new)
+ .map(Group::new)
.collect(Collectors.toSet());
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java
new file mode 100644
index 00000000000..28f486ee055
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java
@@ -0,0 +1,52 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.group.Group;
+
+/**
+ * Jackson-friendly version of {@link Group}.
+ */
+class JsonAdaptedGroup {
+
+ private final String groupName;
+ private final String telegramLink;
+ /**
+ * Constructs a {@code JsonAdaptedGroup} with the given {@code groupName}.
+ */
+ @JsonCreator
+ public JsonAdaptedGroup(@JsonProperty("groupName") String groupName, @JsonProperty("telegramLink") String link) {
+ this.groupName = groupName;
+ this.telegramLink = link;
+ }
+
+ /**
+ * Converts a given {@code Group} into this class for Jackson use.
+ */
+ public JsonAdaptedGroup(Group source) {
+ groupName = source.groupName;
+ telegramLink = source.telegramLink;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted group object into the model's {@code Group} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted group.
+ */
+ public Group toModelType() throws IllegalValueException {
+ if (!Group.isValidGroupName(groupName)) {
+ throw new IllegalValueException(Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ }
+ if (!Group.isValidLink(telegramLink)) {
+ throw new IllegalValueException(Group.MESSAGE_LINK_CONSTRAINTS);
+ }
+ return new Group(groupName, telegramLink);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..299114fbc50 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,12 +10,15 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* Jackson-friendly version of {@link Person}.
@@ -27,22 +30,30 @@ class JsonAdaptedPerson {
private final String name;
private final String phone;
private final String email;
- private final String address;
- private final List tags = new ArrayList<>();
+ private final String year;
+ private final String major;
+ private final String telegram;
+ private final String remark;
+ private final List groups = new ArrayList<>();
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
*/
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ @JsonProperty("email") String email, @JsonProperty("year") String year,
+ @JsonProperty("telegram") String telegram, @JsonProperty("major") String major,
+ @JsonProperty("remark") String remark,
+ @JsonProperty("groups") List groups) {
this.name = name;
this.phone = phone;
this.email = email;
- this.address = address;
- if (tags != null) {
- this.tags.addAll(tags);
+ this.year = year;
+ this.major = major;
+ this.telegram = telegram;
+ this.remark = remark;
+ if (groups != null) {
+ this.groups.addAll(groups);
}
}
@@ -53,9 +64,12 @@ public JsonAdaptedPerson(Person source) {
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
- address = source.getAddress().value;
- tags.addAll(source.getTags().stream()
- .map(JsonAdaptedTag::new)
+ year = source.getYear().value;
+ major = source.getMajor().value;
+ telegram = source.getTelegram().value;
+ remark = source.getRemark().value;
+ groups.addAll(source.getGroups().stream()
+ .map(JsonAdaptedPersonGroupAttendance::new)
.collect(Collectors.toList()));
}
@@ -65,9 +79,9 @@ public JsonAdaptedPerson(Person source) {
* @throws IllegalValueException if there were any data constraints violated in the adapted person.
*/
public Person toModelType() throws IllegalValueException {
- final List personTags = new ArrayList<>();
- for (JsonAdaptedTag tag : tags) {
- personTags.add(tag.toModelType());
+ final List personGroups = new ArrayList<>();
+ for (JsonAdaptedPersonGroupAttendance group : groups) {
+ personGroups.add(group.toModelType());
}
if (name == null) {
@@ -94,16 +108,37 @@ 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()));
+ if (year == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Year.class.getSimpleName()));
}
- if (!Address.isValidAddress(address)) {
- throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
+ if (!Year.isValidYear(year)) {
+ throw new IllegalValueException(Year.MESSAGE_CONSTRAINTS);
}
- final Address modelAddress = new Address(address);
+ final Year modelYear = new Year(year);
- final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ if (telegram == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ Telegram.class.getSimpleName()));
+ }
+ if (!Telegram.isValidTelegram(telegram)) {
+ throw new IllegalValueException(Telegram.MESSAGE_CONSTRAINTS);
+ }
+ final Telegram modelTelegram = new Telegram(telegram);
+
+ if (major == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Major.class.getSimpleName()));
+ }
+ if (!Major.isValidMajor(major)) {
+ throw new IllegalValueException(Major.MESSAGE_CONSTRAINTS);
+ }
+ final Major modelMajor = new Major(major);
+
+ final Remark modelRemark = new Remark(remark);
+
+ final Set modelGroups = new HashSet<>(personGroups);
+
+ return new Person(modelName, modelPhone, modelEmail, modelYear, modelTelegram, modelMajor, modelRemark,
+ modelGroups);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPersonGroupAttendance.java b/src/main/java/seedu/address/storage/JsonAdaptedPersonGroupAttendance.java
new file mode 100644
index 00000000000..35b6d41ebf8
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPersonGroupAttendance.java
@@ -0,0 +1,48 @@
+package seedu.address.storage;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.group.Group;
+
+/**
+ * Jackson-friendly version of {@link Group}.
+ */
+class JsonAdaptedPersonGroupAttendance {
+
+ private final String groupName;
+ private final List attendance;
+ /**
+ * Constructs a {@code JsonAdaptedGroup} with the given {@code groupName}.
+ */
+ @JsonCreator
+ public JsonAdaptedPersonGroupAttendance(@JsonProperty("groupName") String groupName,
+ @JsonProperty("attendance") List attendance) {
+ this.groupName = groupName;
+ this.attendance = attendance;
+ }
+
+ /**
+ * Converts a given {@code Group} into this class for Jackson use.
+ */
+ public JsonAdaptedPersonGroupAttendance(Group source) {
+ groupName = source.groupName;
+ attendance = source.attendance;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted group object into the model's {@code Group} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted group.
+ */
+ public Group toModelType() throws IllegalValueException {
+ if (!Group.isValidGroupName(groupName)) {
+ throw new IllegalValueException(Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ }
+ return new Group(groupName, attendance);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
deleted file mode 100644
index 0df22bdb754..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package seedu.address.storage;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Tag}.
- */
-class JsonAdaptedTag {
-
- private final String tagName;
-
- /**
- * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
- */
- @JsonCreator
- public JsonAdaptedTag(String tagName) {
- this.tagName = tagName;
- }
-
- /**
- * Converts a given {@code Tag} into this class for Jackson use.
- */
- public JsonAdaptedTag(Tag source) {
- tagName = source.tagName;
- }
-
- @JsonValue
- public String getTagName() {
- return tagName;
- }
-
- /**
- * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
- */
- public Tag toModelType() throws IllegalValueException {
- if (!Tag.isValidTagName(tagName)) {
- throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(tagName);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..39177c0ca66 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -11,6 +11,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -22,13 +23,16 @@ class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
private final List persons = new ArrayList<>();
+ private final List groups = new ArrayList<>();
/**
* Constructs a {@code JsonSerializableAddressBook} with the given persons.
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
+ public JsonSerializableAddressBook(@JsonProperty("persons") List persons,
+ @JsonProperty("groups") List groups) {
this.persons.addAll(persons);
+ this.groups.addAll(groups);
}
/**
@@ -38,6 +42,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List 0 && weekNumber <= attendance.length) {
+ return attendance[weekNumber - 1];
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..6883de995ae 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-w10-4.github.io/tp/UserGuide.html#quick-start";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MailWindow.java b/src/main/java/seedu/address/ui/MailWindow.java
new file mode 100644
index 00000000000..d6ed6676de6
--- /dev/null
+++ b/src/main/java/seedu/address/ui/MailWindow.java
@@ -0,0 +1,88 @@
+package seedu.address.ui;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.stage.Stage;
+
+/**
+ * Controller for a mail page
+ */
+public class MailWindow extends UiPart {
+
+ private static final String FXML = "MailWindow.fxml";
+
+ @FXML
+ private Button copyButton;
+
+ @FXML
+ private Label mailLinkLabel;
+
+ private String mailtoLink;
+
+
+ /**
+ * Creates a new MailWindow.
+ *
+ * @param root Stage to use as the root of the MailWindow.
+ */
+ public MailWindow(Stage root) {
+ super(FXML, root);
+ setMailLinkLabel("To email this tutorial group: ");
+ }
+
+ /**
+ * Creates a new HelpWindow.
+ */
+ public MailWindow() {
+ this(new Stage());
+ }
+
+ public void setMailtoLink(String mailtoLink) {
+ this.mailtoLink = mailtoLink;
+ }
+
+ public void setMailLinkLabel(String mailtoLink) {
+ mailLinkLabel.setText(mailtoLink);
+ }
+
+ /**
+ * Shows the help window.
+ */
+ public void show() {
+ getRoot().show();
+ }
+
+ /**
+ * Focuses the help window.
+ */
+ public void focus() {
+ getRoot().requestFocus();
+ }
+
+ /**
+ * Returns true if the help window is currently being shown.
+ */
+ public boolean isShowing() {
+ return getRoot().isShowing();
+ }
+
+ /**
+ * Copies the mailto link to the clipboard.
+ */
+ @FXML
+ private void openMailtoLink() {
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().mail(new URI(this.mailtoLink));
+ } catch (IOException | URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..19406963fb5 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -34,6 +34,7 @@ public class MainWindow extends UiPart {
private PersonListPanel personListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private MailWindow mailWindow;
@FXML
private StackPane commandBoxPlaceholder;
@@ -66,6 +67,7 @@ public MainWindow(Stage primaryStage, Logic logic) {
setAccelerators();
helpWindow = new HelpWindow();
+ mailWindow = new MailWindow();
}
public Stage getPrimaryStage() {
@@ -110,7 +112,7 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
- personListPanel = new PersonListPanel(logic.getFilteredPersonList());
+ personListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.getAddressBook().getGroupList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
resultDisplay = new ResultDisplay();
@@ -147,6 +149,18 @@ public void handleHelp() {
}
}
+ /**
+ * Opens the help window or focuses on it if it's already opened.
+ */
+ @FXML
+ public void handleMail() {
+ if (!mailWindow.isShowing()) {
+ mailWindow.show();
+ } else {
+ mailWindow.focus();
+ }
+ }
+
void show() {
primaryStage.show();
}
@@ -182,6 +196,11 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleHelp();
}
+ if (commandResult.isShowMail()) {
+ mailWindow.setMailtoLink(commandResult.getMailtoLink());
+ handleMail();
+ }
+
if (commandResult.isExit()) {
handleExit();
}
diff --git a/src/main/java/seedu/address/ui/PersonAttendanceList.java b/src/main/java/seedu/address/ui/PersonAttendanceList.java
new file mode 100644
index 00000000000..2609e142c9d
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PersonAttendanceList.java
@@ -0,0 +1,111 @@
+package seedu.address.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.DoubleBinding;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class PersonAttendanceList extends UiPart {
+
+ private static final String FXML = "PersonAttendanceList.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
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Person person;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private FlowPane groups;
+
+ @FXML
+ private TableView attendanceTable;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public PersonAttendanceList(Person person, int displayedIndex) {
+ super(FXML);
+ this.person = person;
+ id.setText(displayedIndex + ". ");
+ name.setText(person.getName().fullName);
+
+ populateGroupNameCol();
+ createAttendanceCols();
+ populateAttendanceCols();
+ groups.getChildren().add(attendanceTable);
+ setTableHt();
+ }
+
+ /**
+ * Creates a new TableColumn for the group column and populates each row with a group.
+ */
+ public void populateGroupNameCol() {
+ TableColumn groupNameColumn = new TableColumn<>("Group");
+ groupNameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getGroupName()));
+ attendanceTable.getColumns().add(0, groupNameColumn);
+ // Set cell value factory for the first column to show group names
+ groupNameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getGroupName()));
+ }
+
+ /**
+ * Create columns of attendance for the table, one for each week.
+ */
+ public void createAttendanceCols() {
+ for (int i = 0; i < Group.MAX_NUM_OF_WEEKS; i++) {
+ int weekNumber = i + 1;
+ TableColumn column = new TableColumn<>("Week " + weekNumber);
+ column.setCellValueFactory(cellData ->
+ new SimpleStringProperty(cellData.getValue().getAttendance(weekNumber)));
+ attendanceTable.getColumns().add(column);
+ }
+ }
+
+ /**
+ * Populates columns with weekly attendance data.
+ */
+ public void populateAttendanceCols() {
+ List attendanceRows = new ArrayList<>();
+ person.getGroups().forEach(group -> {
+ String[] attendanceArray = group.attendance.toArray(new String[0]);
+ attendanceRows.add(new AttendanceRow(group.groupName, attendanceArray));
+ });
+ attendanceTable.getItems().addAll(attendanceRows);
+ }
+
+ /**
+ * Sets the height of the table based on the number of rows.
+ */
+ public void setTableHt() {
+ attendanceTable.setFixedCellSize(45);
+ DoubleBinding tableHeight = Bindings.createDoubleBinding(() ->
+ attendanceTable.getFixedCellSize() * (attendanceTable.getItems().size() + 1),
+ attendanceTable.getItems());
+
+ attendanceTable.prefHeightProperty().bind(tableHeight);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..3e3ce883d6d 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -35,11 +35,17 @@ public class PersonCard extends UiPart {
@FXML
private Label phone;
@FXML
- private Label address;
- @FXML
private Label email;
@FXML
- private FlowPane tags;
+ private Label year;
+ @FXML
+ private Label major;
+ @FXML
+ private Label telegram;
+ @FXML
+ private Label remark;
+ @FXML
+ private FlowPane groups;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -50,10 +56,13 @@ public PersonCard(Person person, int displayedIndex) {
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
- person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ year.setText("Year " + person.getYear().value);
+ major.setText(person.getMajor().value);
+ telegram.setText("@" + person.getTelegram().value);
+ remark.setText(person.getRemark().value);
+ person.getGroups().stream()
+ .sorted(Comparator.comparing(group -> group.groupName))
+ .forEach(group -> groups.getChildren().add(new Label(group.groupName)));
}
}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..0c260e575bc 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -1,13 +1,21 @@
package seedu.address.ui;
+import java.util.HashSet;
+import java.util.Set;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
import javafx.scene.layout.Region;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -19,14 +27,37 @@ public class PersonListPanel extends UiPart {
@FXML
private ListView personListView;
+ @FXML
+ private TabPane tabPane; // Inject the TabPane from FXML
+
+ private ObservableList personList;
+ private ObservableList groupList; // Observable list of groups
+
/**
* Creates a {@code PersonListPanel} with the given {@code ObservableList}.
*/
- public PersonListPanel(ObservableList personList) {
+ public PersonListPanel(ObservableList personList, ObservableList groupList) {
super(FXML);
- personListView.setItems(personList);
- personListView.setCellFactory(listView -> new PersonListViewCell());
+ this.personList = personList;
+ this.groupList = groupList;
+ initializeTabs(); // Initialize tabs after FXML is loaded
+
+ // Add listener to groupList
+ groupList.addListener((ListChangeListener) change -> {
+ while (change.next()) {
+ updateTabs(); // Update tabs whenever there is a change to groups
+ break; // Only update tabs once for each change
+ }
+ });
+
+ // Add listener to personList
+ personList.addListener((ListChangeListener) change -> {
+ while (change.next()) {
+ updateTabs(); // Update tabs whenever there is a change to person
+ break; // Only update tabs once for each change
+ }
+ });
}
/**
@@ -46,4 +77,91 @@ protected void updateItem(Person person, boolean empty) {
}
}
+ class PersonAttendanceViewCell extends ListCell {
+ @Override
+ protected void updateItem(Person person, boolean empty) {
+ super.updateItem(person, empty);
+
+ if (empty || person == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ int absoluteIndex = personList.indexOf(person) + 1;
+ String selectedTabName = (tabPane.getSelectionModel().getSelectedItem() != null)
+ ? tabPane.getSelectionModel().getSelectedItem().getText() : "Results";
+ setGraphic(new PersonAttendanceList(person, absoluteIndex).getRoot());
+ }
+ }
+ }
+
+ /**
+ * Creates a tab to display all persons.
+ */
+ private void createAllTab() {
+ Tab allTab = new Tab("Results");
+ ListView allListView = new ListView<>();
+ allListView.setItems(personList);
+ allListView.setCellFactory(listView -> new PersonListViewCell());
+ allTab.setContent(allListView);
+ tabPane.getTabs().add(allTab);
+ }
+
+ /**
+ * Creates a tab for each group and display all persons in that group.
+ * @param groups
+ */
+ private void createEachGroupTab(Set groups) {
+ for (Group group : groups) {
+ Tab tab = new Tab(group.groupName);
+ ListView groupListView = new ListView<>();
+ tab.setContent(groupListView);
+
+ // Filter persons based on the group and set them in the ListView
+ ObservableList filteredPersons = personList.filtered(person -> person.getGroups().contains(group));
+ ObservableList personsWithFilteredGroups = FXCollections.observableArrayList();
+
+ for (Person person : filteredPersons) {
+ Set filteredGroups = person.getGroups().stream()
+ .filter(g -> g.equals(group)).collect(Collectors.toSet());
+ Person newPerson = new Person(person.getName(), person.getPhone(), person.getEmail(), person.getYear(),
+ person.getTelegram(), person.getMajor(), person.getRemark(), filteredGroups);
+ personsWithFilteredGroups.add(newPerson);
+ }
+
+ groupListView.setItems(personsWithFilteredGroups);
+ groupListView.setCellFactory(listView -> new PersonAttendanceViewCell());
+ tabPane.getTabs().add(tab);
+ }
+ }
+
+ /**
+ * Initializes the tabs with the list of students from each group
+ */
+ private void initializeTabs() {
+ if (tabPane == null) {
+ throw new AssertionError("TabPane is not injected.");
+ }
+
+ // Clear existing tabs
+ tabPane.getTabs().clear();
+
+ // Creates a tab for displaying all persons
+ createAllTab();
+
+ // Get the set of all unique groups from the group list
+ Set groups = new HashSet<>(groupList);
+
+ createEachGroupTab(groups);
+ }
+
+ /**
+ * Updates the tabs and the person list in each tab
+ */
+ private void updateTabs() {
+ // Clear existing tabs
+ tabPane.getTabs().clear();
+
+ // Reinitialize tabs with updated group list
+ initializeTabs();
+ }
}
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..fd224106f1c 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -1,6 +1,6 @@
.background {
- -fx-background-color: derive(#1d1d1d, 20%);
- background-color: #383838; /* Used in the default.html file */
+ -fx-background-color: derive(#104A53, 20%);
+ background-color: #FFFFFF; /* Used in the default.html file */
}
.label {
@@ -29,29 +29,55 @@
-fx-font-family: "Segoe UI Semibold";
}
+.tab-label {
+ -fx-text-fill: black;
+}
+
.tab-pane {
- -fx-padding: 0 0 0 1;
+ -fx-padding: 0 0 0 0;
+ -fx-font-size: 14px;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-background-color: #104A53;
}
-.tab-pane .tab-header-area {
+.tab-pane .tab-header-area .tab-header-background {
-fx-padding: 0 0 0 0;
-fx-min-height: 0;
-fx-max-height: 0;
+ -fx-background-color: derive(#104A53, 20%);
+}
+
+.tab:selected {
+ -fx-background-color: #B0EDE2;
+}
+
+.tab:hover {
+ -fx-background-color: derive(#B0EDE2, 40%);
}
.table-view {
- -fx-base: #1d1d1d;
- -fx-control-inner-background: #1d1d1d;
- -fx-background-color: #1d1d1d;
+ -fx-base: #104A53;
+ -fx-control-inner-background: #104A53;
+ -fx-background-color: #104A53;
-fx-table-cell-border-color: transparent;
-fx-table-header-border-color: transparent;
- -fx-padding: 5;
+ -fx-padding: 0;
+}
+
+.table-cell {
+ -fx-alignment: CENTER;
+ -fx-font-size: 8pt;
}
.table-view .column-header-background {
-fx-background-color: transparent;
}
+.table-column {
+ -fx-border-width: 0 1px 0 0; /* Top, right, bottom, left */
+ -fx-border-color: black;
+}
+
.table-view .column-header, .table-view .filler {
-fx-size: 35;
-fx-border-width: 0 0 1 0;
@@ -65,59 +91,85 @@
}
.table-view .column-header .label {
- -fx-font-size: 20pt;
- -fx-font-family: "Segoe UI Light";
- -fx-text-fill: white;
- -fx-alignment: center-left;
+ -fx-font-size: 40pt;
+ -fx-font-family: "Segoe UI";
+ -fx-alignment: CENTER;
-fx-opacity: 1;
+ -fx-table-cell-border-color: white transparent white transparent;
+}
+
+.table-view {
+ -fx-border-width: 1px;
+ -fx-border-color: transparent;
+}
+
+.table-row-cell {
+ -fx-table-cell-border-color: black;
+}
+
+.table-row-cell:odd {
+ -fx-background-color: derive(#104A53, 40%);
+}
+
+.table-row-cell:even {
+ -fx-background-color: derive(#104A53, 60%);
+}
+
+.table-row-cell:empty {
+ -fx-background-color: #104A53;
+}
+
+.table-row-cell:empty .table-cell {
+ -fx-border-width: 0px;
+}
+
+.table-row-cell:selected .table-cell {
+ -fx-text-fill: black;
}
.table-view:focused .table-row-cell:filled:focused:selected {
- -fx-background-color: -fx-focus-color;
+ -fx-background-color: #B0EDE2;
}
.split-pane:horizontal .split-pane-divider {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: transparent transparent transparent #4d4d4d;
+ -fx-background-color: derive(#E6ECF0, 20%);
+ -fx-border-color: transparent transparent transparent #FFFFFF;
+ -fx-border-insets: 4 4 4 4;
}
.split-pane {
-fx-border-radius: 1;
-fx-border-width: 1;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#104A53, 20%);
+ -fx-border-insets: 4 4 4 4;
}
.list-view {
-fx-background-insets: 0;
- -fx-padding: 0;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-padding: 5;
+ -fx-background-color: derive(#104A53, 20%);
}
.list-cell {
-fx-label-padding: 0 0 0 0;
-fx-graphic-text-gap : 0;
- -fx-padding: 0 0 0 0;
+ -fx-padding: 4px;
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: #B0EDE2;
+ -fx-background-radius: 20px;
+ -fx-background-insets: 4 0 4 0;
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
-}
-
-.list-cell:filled:selected {
- -fx-background-color: #424d5f;
-}
-
-.list-cell:filled:selected #cardPane {
- -fx-border-color: #3e7b91;
- -fx-border-width: 1;
+ -fx-background-color: derive(#B0EDE2, 20%);
+ -fx-background-radius: 20px;
+ -fx-background-insets: 4 0 4 0;
}
.list-cell .label {
- -fx-text-fill: white;
+ -fx-text-fill: black;
}
.cell_big_label {
@@ -133,17 +185,21 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#104A53, 20%);
+ -fx-border-insets: 10 10 10 10;
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-background-color: derive(#104A53, 20%);
+ -fx-border-color: derive(#104A53, 10%);
-fx-border-top-width: 1px;
+ -fx-border-radius: 2px;
+ -fx-background-radius: 2px;
+ -fx-border-insets: 4 4 4 4;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #104A53;
}
.result-display {
@@ -151,6 +207,7 @@
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
+ -fx-border-insets: 10 10 10 10;
}
.result-display .label {
@@ -165,27 +222,27 @@
}
.status-bar-with-border {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#104A53, 30%);
+ -fx-border-color: derive(#104A53, 25%);
-fx-border-width: 1px;
}
.status-bar-with-border .label {
- -fx-text-fill: white;
+ -fx-text-fill: #104A53;
}
.grid-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#104A53, 30%);
+ -fx-border-color: derive(#104A53, 30%);
-fx-border-width: 1px;
}
.grid-pane .stack-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#104A53, 30%);
}
.context-menu {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: derive(#104A53, 50%);
}
.context-menu .label {
@@ -193,7 +250,7 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#FFFFFF, 20%);
}
.menu-bar .label {
@@ -257,11 +314,11 @@
}
.dialog-pane {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #FFFFFF;
}
.dialog-pane > *.button-bar > *.container {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #FFFFFF;
}
.dialog-pane > *.label.content {
@@ -271,7 +328,7 @@
}
.dialog-pane:header *.header-panel {
- -fx-background-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#FFFFFF, 25%);
}
.dialog-pane:header *.header-panel *.label {
@@ -282,11 +339,11 @@
}
.scroll-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#FFFFFF, 20%);
}
.scroll-bar .thumb {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: derive(#104A53, 50%);
-fx-background-insets: 3;
}
@@ -300,7 +357,7 @@
}
.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
- -fx-padding: 1 8 1 8;
+ -fx-padding: 1 6 1 6;
}
.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
@@ -318,35 +375,73 @@
}
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-color: #FFFFFF;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
-fx-border-insets: 0;
- -fx-border-width: 1;
+ -fx-border-width: 0;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
- -fx-text-fill: white;
+ -fx-text-fill: black;
}
#filterField, #personListPanel, #personWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
+#resultDisplay {
+ -fx-background-color: #104A53;
+ -fx-text-fill: black;
+ -fx-background-radius: 1px;
+ -fx-border-radius: 2px;
+}
+
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-color: derive(#D4FBEF, 0%);
-fx-background-radius: 0;
}
-#tags {
+#groups {
-fx-hgap: 7;
-fx-vgap: 3;
}
-#tags .label {
+#groups .label {
-fx-text-fill: white;
- -fx-background-color: #3e7b91;
- -fx-padding: 1 3 1 3;
- -fx-border-radius: 2;
- -fx-background-radius: 2;
+ -fx-background-color: #104A53;
+ -fx-padding: 2 4 2 4;
+ -fx-border-radius: 1px;
+ -fx-background-radius: 1px;
-fx-font-size: 11;
}
+
+#personName {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#personName .label {
+ -fx-text-fill: white;
+ -fx-background-color: #104A53;
+ -fx-padding: 2 4 2 4;
+ -fx-border-radius: 10px;
+ -fx-background-radius: 10px;
+ -fx-font-size: 11;
+}
+
+#personListTitle, #groupListTitle {
+ -fx-text-fill: white;
+ -fx-padding: 4 20 4 20;
+}
+
+.list-cell:filled:selected #taskContainer {
+ -fx-border-color: #3e7b91;
+ -fx-border-width: 0;
+}
+
+.scroll-pane > .viewport {
+ -fx-background-color: transparent;
+}
+
+.scroll-pane {
+ -fx-background-color: transparent;
+}
diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css
index bfe82a85964..2103b8546f2 100644
--- a/src/main/resources/view/Extensions.css
+++ b/src/main/resources/view/Extensions.css
@@ -8,7 +8,7 @@
-fx-background: #383838;
}
-.tag-selector {
+.group-selector {
-fx-border-width: 1;
-fx-border-color: white;
-fx-border-radius: 3;
diff --git a/src/main/resources/view/MailWindow.fxml b/src/main/resources/view/MailWindow.fxml
new file mode 100644
index 00000000000..55aa109e28b
--- /dev/null
+++ b/src/main/resources/view/MailWindow.fxml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..030a5ab473a 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -10,11 +10,12 @@
+
-
+
+
-
+
@@ -23,8 +24,8 @@
-
-
+
+
@@ -33,27 +34,29 @@
-
+
-
+
-
+
+
-
+
-
+
+
-
+
-
+
+
-
+
diff --git a/src/main/resources/view/PersonAttendanceList.fxml b/src/main/resources/view/PersonAttendanceList.fxml
new file mode 100644
index 00000000000..28a6a11d95f
--- /dev/null
+++ b/src/main/resources/view/PersonAttendanceList.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f5e812e25e6..85e33bc2376 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -1,36 +1,37 @@
-
-
-
-
-
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml
index a1bb6bbace8..e8659cf8ec6 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/PersonListPanel.fxml
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..c9cc4cb5ec5 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -3,11 +3,18 @@
"name": "Valid Person",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "validperson",
+ "remark": "valid remark"
}, {
"name": "Person With Invalid Phone Field",
"phone": "948asdf2424",
"email": "hans@example.com",
- "address": "4th street"
- } ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "invalidperson",
+ "remark": "remark is always valid"
+ } ],
+ "groups": []
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..d7b1fb447b6 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -3,6 +3,10 @@
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
- } ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "personwithinvalidname",
+ "remark": "remark is always valid"
+ } ],
+ "groups": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..1a2fc0a4a62 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -3,12 +3,24 @@
"name": "Alice Pauline",
"phone": "94351253",
"email": "alice@example.com",
- "address": "123, Jurong West Ave 6, #08-111",
- "tags": [ "friends" ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "alicepauline",
+ "remark": "valid remark",
+ "groups": [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "A", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
- "address": "4th street"
- } ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "alicepauline",
+ "remark": "valid remark"
+ } ],
+ "groups": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..16760817faa 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,9 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
- } ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "hansmuster"
+ } ],
+ "groups": []
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json
new file mode 100644
index 00000000000..37d963d3cb3
--- /dev/null
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json
@@ -0,0 +1,13 @@
+{
+ "persons": [],
+ "groups": [
+ {
+ "groupName": "TUT04",
+ "telegramLink": ""
+ },
+ {
+ "groupName": "LAB10",
+ "telegramLink": ""
+ }
+ ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..f38450b6249 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -4,43 +4,108 @@
"name" : "Alice Pauline",
"phone" : "94351253",
"email" : "alice@example.com",
- "address" : "123, Jurong West Ave 6, #08-111",
- "tags" : [ "friends" ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "alicepauline",
+ "remark": "shy",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ },
+ {
+ "groupName" : "LAB10",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
- "address" : "311, Clementi Ave 2, #02-25",
- "tags" : [ "owesMoney", "friends" ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "bensonmeier",
+ "remark": "always skip tutorials",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ },
+ {
+ "groupName" : "LAB10",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
- "address" : "wall street",
- "tags" : [ ]
+ "year": "1",
+ "major": "Computer Science",
+ "telegram": "carlkurz",
+ "remark": "",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
- "email" : "cornelia@example.com",
- "address" : "10th street",
- "tags" : [ "friends" ]
+ "email" : "corelia@example.com",
+ "year": "1",
+ "major": "Computer Engineering",
+ "telegram": "danielmeier",
+ "remark": "hardworking",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
- "address" : "michegan ave",
- "tags" : [ ]
+ "year": "2",
+ "major": "Computer Engineering",
+ "telegram": "ellemeyer",
+ "remark": "strong foundations",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
- "address" : "little tokyo",
- "tags" : [ ]
+ "year": "2",
+ "major": "Business Analytics",
+ "telegram": "fionakunz1",
+ "remark": "Always ask questions in class",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
- "address" : "4th street",
- "tags" : [ ]
- } ]
+ "year": "2",
+ "major": "Business Analytics",
+ "telegram": "georgebest1",
+ "remark": "",
+ "groups" : [
+ {
+ "groupName" : "TUT04",
+ "attendance" : [ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_" ]
+ }
+ ]
+ } ],
+ "groups": []
}
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..d27ed1769b6 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -46,7 +46,7 @@ public void isNonZeroUnsignedInteger() {
}
- //---------------- Tests for containsWordIgnoreCase --------------------------------------
+ //---------------- Tests for containsStartingWordIgnoreCase --------------------------------------
/*
* Invalid equivalence partitions for word: null, empty, multiple words
@@ -55,25 +55,27 @@ public void isNonZeroUnsignedInteger() {
*/
@Test
- public void containsWordIgnoreCase_nullWord_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase("typical sentence", null));
+ public void containsStartingWordIgnoreCase_nullWord_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> StringUtil.containsStartingWordIgnoreCase(
+ "typical sentence", null));
}
@Test
- public void containsWordIgnoreCase_emptyWord_throwsIllegalArgumentException() {
+ public void containsStartingWordIgnoreCase_emptyWord_throwsIllegalArgumentException() {
assertThrows(IllegalArgumentException.class, "Word parameter cannot be empty", ()
- -> StringUtil.containsWordIgnoreCase("typical sentence", " "));
+ -> StringUtil.containsStartingWordIgnoreCase("typical sentence", " "));
}
@Test
- public void containsWordIgnoreCase_multipleWords_throwsIllegalArgumentException() {
+ public void containsStartingWordIgnoreCase_multipleWords_throwsIllegalArgumentException() {
assertThrows(IllegalArgumentException.class, "Word parameter should be a single word", ()
- -> StringUtil.containsWordIgnoreCase("typical sentence", "aaa BBB"));
+ -> StringUtil.containsStartingWordIgnoreCase("typical sentence", "aaa BBB"));
}
@Test
- public void containsWordIgnoreCase_nullSentence_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase(null, "abc"));
+ public void containsStartingWordIgnoreCase_nullSentence_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> StringUtil.containsStartingWordIgnoreCase(null,
+ "abc"));
}
/*
@@ -93,9 +95,10 @@ public void containsWordIgnoreCase_nullSentence_throwsNullPointerException() {
* - last word in sentence
* - middle word in sentence
* - matches multiple words
+ * - query word matches part of a sentence word
*
* Possible scenarios returning false:
- * - query word matches part of a sentence word
+ * -
* - sentence word matches part of the query word
*
* The test method below tries to verify all above with a reasonably low number of test cases.
@@ -105,22 +108,31 @@ public void containsWordIgnoreCase_nullSentence_throwsNullPointerException() {
public void containsWordIgnoreCase_validInputs_correctResult() {
// Empty sentence
- assertFalse(StringUtil.containsWordIgnoreCase("", "abc")); // Boundary case
- assertFalse(StringUtil.containsWordIgnoreCase(" ", "123"));
+ assertFalse(StringUtil.containsStartingWordIgnoreCase("", "abc")); // Boundary case
+ assertFalse(StringUtil.containsStartingWordIgnoreCase(" ", "123"));
- // Matches a partial word only
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word
+ // Matches a partial word only and starts with query word
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("anna betty carol", "car"));
+ // Query word starts with sentence word
+ assertFalse(StringUtil.containsStartingWordIgnoreCase("anna betty carol", "na"));
+ // Query word does not start with sentence word
+ assertFalse(StringUtil.containsStartingWordIgnoreCase("aaa bbb ccc", "bbbb"));
+ // Query word bigger than sentence word
// Matches word in the sentence, different upper/lower case letters
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc", "Bbb")); // First word (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc@1", "CCc@1")); // Last word (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase(" AAA bBb ccc ", "aaa")); // Sentence has extra spaces
- assertTrue(StringUtil.containsWordIgnoreCase("Aaa", "aaa")); // Only one word in sentence (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", " ccc ")); // Leading/trailing spaces
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("aaa bBb ccc", "Bbb"));
+ // First word (boundary case)
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("aaa bBb ccc@1", "CCc@1"));
+ // Last word (boundary case)
+ assertTrue(StringUtil.containsStartingWordIgnoreCase(" AAA bBb ccc ", "aaa"));
+ // Sentence has extra spaces
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("Aaa", "aaa"));
+ // Only one word in sentence (boundary case)
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("aaa bbb ccc", " ccc "));
+ // Leading/trailing spaces
// Matches multiple words in sentence
- assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc bbb", "bbB"));
+ assertTrue(StringUtil.containsStartingWordIgnoreCase("AAA bBb ccc bbb", "bbB"));
}
//---------------- Tests for getDetails --------------------------------------
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..0f836978146 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -3,10 +3,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.MAJOR_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.REMARK_DESC_SHY;
+import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.YEAR_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -165,9 +168,10 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
logic = new LogicManager(model, storage);
// Triggers the saveAddressBook method by executing an add command
- String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + YEAR_DESC_AMY
+ + TELEGRAM_DESC_AMY + EMAIL_DESC_AMY + MAJOR_DESC_AMY + REMARK_DESC_SHY;
+
+ Person expectedPerson = new PersonBuilder(AMY).withGroups().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index 162a0c86031..e2f9d9f59ca 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -2,7 +2,7 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -11,6 +11,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -45,4 +46,27 @@ public void execute_duplicatePerson_throwsCommandException() {
AddCommand.MESSAGE_DUPLICATE_PERSON);
}
+ @Test
+ public void execute_validGroup_throwsCommandException() {
+ Person validPerson = new PersonBuilder().withGroups("LAB10").build();
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.addPerson(validPerson);
+
+ assertCommandSuccess(new AddCommand(validPerson), model,
+ String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)),
+ expectedModel);
+ }
+
+ @Test
+ public void execute_invalidGroup_throwsCommandException() {
+ Person invalidGroup = new PersonBuilder().withGroups("LAB02").build();
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.addPerson(invalidGroup);
+
+ assertCommandFailure(new AddCommand(invalidGroup), model,
+ String.format(Group.MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK, "[LAB02]"));
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..613a2c5861e 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -22,6 +22,7 @@
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -148,6 +149,26 @@ public void setPerson(Person target, Person editedPerson) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public boolean hasGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteGroup(Group target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGroup(Group target, Group editedGroup) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public ObservableList getFilteredPersonList() {
throw new AssertionError("This method should not be called.");
diff --git a/src/test/java/seedu/address/logic/commands/AddGroupCommandTest.java b/src/test/java/seedu/address/logic/commands/AddGroupCommandTest.java
new file mode 100644
index 00000000000..faafd19a4e8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddGroupCommandTest.java
@@ -0,0 +1,224 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.LAB10;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+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.ReadOnlyUserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+public class AddGroupCommandTest {
+
+ @Test
+ public void constructor_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddGroupCommand(null));
+ }
+
+ @Test
+ public void execute_groupAcceptedByModel_addSuccessful() throws Exception {
+ ModelStubAcceptingGroupAdded modelStub = new ModelStubAcceptingGroupAdded();
+ Group validGroup = new Group("TUT10");
+
+ CommandResult commandResult = new AddGroupCommand(validGroup).execute(modelStub);
+
+ assertEquals(String.format(AddGroupCommand.MESSAGE_SUCCESS, validGroup),
+ commandResult.getFeedbackToUser());
+ assertEquals(Arrays.asList(validGroup), modelStub.groupsAdded);
+ }
+
+ @Test
+ public void execute_duplicateGroup_throwsCommandException() {
+ Group validGroup = new Group("TUT10");
+ AddGroupCommand addGroupCommand = new AddGroupCommand(validGroup);
+ ModelStub modelStub = new ModelStubWithGroup(validGroup);
+
+ assertThrows(CommandException.class,
+ AddGroupCommand.MESSAGE_DUPLICATE_GROUP, () -> addGroupCommand.execute(modelStub));
+ }
+
+ @Test
+ public void equals() {
+ Group tut01 = new Group("TUT01");
+ Group lab01 = new Group("LAB01");
+ AddGroupCommand addTut01Command = new AddGroupCommand(tut01);
+ AddGroupCommand addLab01Command = new AddGroupCommand(lab01);
+
+ // same object -> returns true
+ assertTrue(addTut01Command.equals(addTut01Command));
+
+ // same values -> returns true
+ AddGroupCommand addTut01CommandCopy = new AddGroupCommand(tut01);
+ assertTrue(addTut01Command.equals(addTut01CommandCopy));
+
+ // different types -> returns false
+ assertFalse(addTut01Command.equals(1));
+
+ // null -> returns false
+ assertFalse(addTut01Command.equals(null));
+
+ // different person -> returns false
+ assertFalse(addTut01Command.equals(addLab01Command));
+ }
+
+ @Test
+ public void toStringMethod() {
+ AddGroupCommand addGroupCommand = new AddGroupCommand(LAB10);
+ String expected = AddGroupCommand.class.getCanonicalName() + "{toAdd=" + LAB10 + "}";
+ assertEquals(expected, addGroupCommand.toString());
+ }
+
+ /**
+ * A default model stub that have all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteGroup(Group target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGroup(Group target, Group editedGroup) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single person.
+ */
+ private class ModelStubWithGroup extends ModelStub {
+ private final Group group;
+
+ ModelStubWithGroup(Group group) {
+ requireNonNull(group);
+ this.group = group;
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return this.group.isSameGroup(group);
+ }
+ }
+
+ /**
+ * A Model stub that always accept the person being added.
+ */
+ private class ModelStubAcceptingGroupAdded extends ModelStub {
+ final ArrayList groupsAdded = new ArrayList<>();
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return groupsAdded.stream().anyMatch(group::isSameGroup);
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ requireNonNull(group);
+ groupsAdded.add(group);
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ return new AddressBook();
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..126b56ad8ca 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -1,7 +1,7 @@
package seedu.address.logic.commands;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
index 7b8c7cd4546..19a9e5e5c91 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
@@ -14,7 +14,7 @@ public void equals() {
// same values -> returns true
assertTrue(commandResult.equals(new CommandResult("feedback")));
- assertTrue(commandResult.equals(new CommandResult("feedback", false, false)));
+ assertTrue(commandResult.equals(new CommandResult("feedback", false, false, false)));
// same object -> returns true
assertTrue(commandResult.equals(commandResult));
@@ -29,10 +29,13 @@ public void equals() {
assertFalse(commandResult.equals(new CommandResult("different")));
// different showHelp value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", true, false)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", true, false, false)));
+
+ // different showMail value -> returns false
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, true, false)));
// different exit value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", false, true)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, false, true)));
}
@Test
@@ -46,10 +49,13 @@ public void hashcode() {
assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode());
// different showHelp value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false, false).hashCode());
+
+ // different showMail value -> returns different hashcode
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true, false).hashCode());
// different exit value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, false, true).hashCode());
}
@Test
@@ -57,7 +63,7 @@ public void toStringMethod() {
CommandResult commandResult = new CommandResult("feedback");
String expected = CommandResult.class.getCanonicalName() + "{feedbackToUser="
+ commandResult.getFeedbackToUser() + ", showHelp=" + commandResult.isShowHelp()
- + ", exit=" + commandResult.isExit() + "}";
+ + ", showMail=" + commandResult.isShowMail() + ", exit=" + commandResult.isExit() + "}";
assertEquals(expected, commandResult.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..8c383a2deaf 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -2,11 +2,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import static seedu.address.testutil.Assert.assertThrows;
import java.util.ArrayList;
@@ -32,10 +35,17 @@ public class CommandTestUtil {
public static final String VALID_PHONE_BOB = "22222222";
public static final String VALID_EMAIL_AMY = "amy@example.com";
public static final String VALID_EMAIL_BOB = "bob@example.com";
- public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
- public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
- public static final String VALID_TAG_HUSBAND = "husband";
- public static final String VALID_TAG_FRIEND = "friend";
+ public static final String VALID_YEAR_AMY = "1";
+ public static final String VALID_YEAR_BOB = "2";
+ public static final String VALID_MAJOR_AMY = "Computer Science";
+ public static final String VALID_MAJOR_BOB = "Business Analytics";
+ public static final String VALID_TELEGRAM_AMY = "amy123";
+ public static final String VALID_TELEGRAM_BOB = "bob456";
+ public static final String VALID_REMARK_SHY = "shy";
+ public static final String VALID_REMARK_OUTSPOKEN = "outspoken";
+ public static final String VALID_GROUP_TUTORIAL = "TUT04";
+ public static final String VALID_GROUP_LAB = "LAB10";
+ public static final String VALID_TELEGRAM_LINK = "https://t.me/abcdefghijklmnopqrstu";
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
@@ -43,16 +53,24 @@ public class CommandTestUtil {
public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB;
public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY;
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
- public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
- public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
- public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
- public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
+ public static final String YEAR_DESC_AMY = " " + PREFIX_YEAR + VALID_YEAR_AMY;
+ public static final String YEAR_DESC_BOB = " " + PREFIX_YEAR + VALID_YEAR_BOB;
+ public static final String MAJOR_DESC_AMY = " " + PREFIX_MAJOR + VALID_MAJOR_AMY;
+ public static final String MAJOR_DESC_BOB = " " + PREFIX_MAJOR + VALID_MAJOR_BOB;
+ public static final String TELEGRAM_DESC_AMY = " " + PREFIX_TELEGRAM + VALID_TELEGRAM_AMY;
+ public static final String TELEGRAM_DESC_BOB = " " + PREFIX_TELEGRAM + VALID_TELEGRAM_BOB;
+ public static final String REMARK_DESC_SHY = " " + PREFIX_REMARK + VALID_REMARK_SHY;
+ public static final String REMARK_DESC_OUTSPOKEN = " " + PREFIX_REMARK + VALID_REMARK_OUTSPOKEN;
+ public static final String GROUP_DESC_TUTORIAL = " " + PREFIX_GROUP + VALID_GROUP_TUTORIAL;
+ public static final String GROUP_DESC_LAB = " " + PREFIX_GROUP + VALID_GROUP_LAB;
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
- public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
- public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
+ public static final String INVALID_YEAR_DESC = " " + PREFIX_YEAR + "7";
+ public static final String INVALID_MAJOR_DESC = " " + PREFIX_MAJOR; // empty string not allowed for major
+ public static final String INVALID_TELEGRAM_DESC = " " + PREFIX_TELEGRAM + "inval!d";
+ public static final String INVALID_GROUP_DESC = " " + PREFIX_GROUP + "TUT903029"; // 'group name should be 2 digits'
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
@@ -62,11 +80,11 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withMajor(VALID_MAJOR_AMY)
+ .withRemark(VALID_REMARK_SHY).withGroups(VALID_GROUP_TUTORIAL).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withMajor(VALID_MAJOR_BOB)
+ .withRemark(VALID_REMARK_OUTSPOKEN).withGroups(VALID_GROUP_LAB, VALID_GROUP_TUTORIAL).build();
}
/**
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..9972e6d344b 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -6,9 +6,9 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java
new file mode 100644
index 00000000000..cb2d0e30b06
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java
@@ -0,0 +1,232 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.LAB10;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+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.ReadOnlyUserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+public class DeleteGroupCommandTest {
+
+ @Test
+ public void constructor_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new DeleteGroupCommand(null));
+ }
+
+ @Test
+ public void execute_groupIsDeletedFromModel_deleteSuccessful() throws Exception {
+ ModelStubAcceptingGroupAdded modelStub = new ModelStubAcceptingGroupAdded();
+ Group validGroup = new Group("TUT10");
+ modelStub.addGroup(validGroup);
+
+ CommandResult commandResult = new DeleteGroupCommand(validGroup).execute(modelStub);
+
+ assertEquals(String.format(DeleteGroupCommand.MESSAGE_SUCCESS, validGroup),
+ commandResult.getFeedbackToUser());
+ assertEquals(Collections.emptyList(), modelStub.groupsAdded);
+ }
+
+ @Test
+ public void execute_groupNotInAddressBook_throwsCommandException() {
+ ModelStubAcceptingGroupAdded modelStub = new ModelStubAcceptingGroupAdded();
+ Group validGroup = new Group("TUT10");
+ modelStub.addGroup(validGroup);
+
+ DeleteGroupCommand deleteGroupCommand = new DeleteGroupCommand(new Group("LAB01"));
+ assertThrows(CommandException.class,
+ DeleteGroupCommand.MESSAGE_NOT_FOUND, () -> deleteGroupCommand.execute(modelStub));
+ }
+
+ @Test
+ public void equals() {
+ Group tut01 = new Group("TUT01");
+ Group lab01 = new Group("LAB01");
+ DeleteGroupCommand deleteTut01Command = new DeleteGroupCommand(tut01);
+ DeleteGroupCommand deleteLab01Command = new DeleteGroupCommand(lab01);
+
+ // same object -> returns true
+ assertTrue(deleteTut01Command.equals(deleteTut01Command));
+
+ // same values -> returns true
+ DeleteGroupCommand deleteTut01CommandCopy = new DeleteGroupCommand(tut01);
+ assertTrue(deleteTut01Command.equals(deleteTut01CommandCopy));
+
+ // different types -> returns false
+ assertFalse(deleteTut01Command.equals(1));
+
+ // null -> returns false
+ assertFalse(deleteTut01Command.equals(null));
+
+ // different person -> returns false
+ assertFalse(deleteTut01Command.equals(deleteLab01Command));
+ }
+
+ @Test
+ public void toStringMethod() {
+ DeleteGroupCommand deleteGroupCommand = new DeleteGroupCommand(LAB10);
+ String expected = DeleteGroupCommand.class.getCanonicalName() + "{toRemove=" + LAB10 + "}";
+ assertEquals(expected, deleteGroupCommand.toString());
+ }
+
+ /**
+ * A default model stub that have all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteGroup(Group target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGroup(Group target, Group editedGroup) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single person.
+ */
+ private class ModelStubWithGroup extends ModelStub {
+ private final Group group;
+
+ ModelStubWithGroup(Group group) {
+ requireNonNull(group);
+ this.group = group;
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return this.group.isSameGroup(group);
+ }
+ }
+
+ /**
+ * A Model stub that always accept the person being added.
+ */
+ private class ModelStubAcceptingGroupAdded extends ModelStub {
+ final ArrayList groupsAdded = new ArrayList<>();
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return groupsAdded.stream().anyMatch(group::isSameGroup);
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ requireNonNull(group);
+ groupsAdded.add(group);
+ }
+
+ @Override
+ public void deleteGroup(Group group) {
+ requireNonNull(group);
+ groupsAdded.remove(group);
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ return new AddressBook();
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..82c893a42fe 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -5,15 +5,15 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
@@ -24,6 +24,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -56,10 +57,10 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
PersonBuilder personInList = new PersonBuilder(lastPerson);
Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withTags(VALID_TAG_HUSBAND).build();
+ .withGroups(VALID_GROUP_LAB).build();
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
+ .withPhone(VALID_PHONE_BOB).withGroups(VALID_GROUP_LAB).build();
EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
@@ -129,6 +130,58 @@ public void execute_invalidPersonIndexUnfilteredList_failure() {
assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_invalidGroupFilteredList_failure() {
+ // edit person in filtered list into a duplicate in address book
+ Person personInList = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased());
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder(personInList).withGroups("TUT01").build());
+
+ assertCommandFailure(editCommand, model, String.format(Group.MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK, "[TUT01]"));
+ }
+
+ @Test
+ public void execute_invalidGroupUnfilteredList_failure() {
+ // edit person in filtered list into a duplicate in address book
+ Person personInList = model.getAddressBook().getPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder(personInList).withGroups("TUT01").build());
+
+ assertCommandFailure(editCommand, model, String.format(Group.MESSAGE_GROUP_NOT_IN_ADDRESS_BOOK, "[TUT01]"));
+ }
+
+ @Test
+ public void execute_validGroupFilteredList_success() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Person editedPerson = new PersonBuilder(personInFilteredList).withGroups("LAB10").build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder().withGroups("LAB10").build());
+
+ String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validGroupUnfilteredList_success() {
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder().withGroups("LAB10").build());
+
+ Person personInList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Person editedPerson = new PersonBuilder(personInList).withGroups("LAB10").build();
+
+ String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
/**
* Edit filtered list where index is larger than size of filtered list,
* but smaller than size of address book
@@ -143,7 +196,7 @@ public void execute_invalidPersonIndexFilteredList_failure() {
EditCommand editCommand = new EditCommand(outOfBoundIndex,
new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(editCommand, model, (Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX));
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/EditGroupCommandTest.java b/src/test/java/seedu/address/logic/commands/EditGroupCommandTest.java
new file mode 100644
index 00000000000..38fd952c12d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditGroupCommandTest.java
@@ -0,0 +1,234 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.LAB10;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+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.ReadOnlyUserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+public class EditGroupCommandTest {
+
+ @Test
+ public void constructor_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new EditGroupCommand(null, null));
+ }
+
+ @Test
+ public void execute_groupIsEdited_editSuccessful() throws Exception {
+ ModelStubAcceptingGroupAdded modelStub = new ModelStubAcceptingGroupAdded();
+ Group validGroup = new Group("TUT10");
+ modelStub.addGroup(validGroup);
+
+ CommandResult commandResult = new EditGroupCommand(validGroup, "").execute(modelStub);
+
+ assertEquals(String.format(EditGroupCommand.MESSAGE_SUCCESS, validGroup),
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_groupNotInAddressBook_throwsCommandException() {
+ ModelStubAcceptingGroupAdded modelStub = new ModelStubAcceptingGroupAdded();
+ Group validGroup = new Group("TUT10");
+ modelStub.addGroup(validGroup);
+
+ EditGroupCommand deleteGroupCommand = new EditGroupCommand(new Group("LAB01"), "");
+ assertThrows(CommandException.class,
+ EditGroupCommand.MESSAGE_NOT_FOUND, () -> deleteGroupCommand.execute(modelStub));
+ }
+
+ @Test
+ public void equals() {
+ Group tut01 = new Group("TUT01");
+ Group lab01 = new Group("LAB01");
+ EditGroupCommand editTut01Command = new EditGroupCommand(tut01, "https://t.me/abcdefg");
+ EditGroupCommand editLab01Command = new EditGroupCommand(lab01, "https://t.me/abcedfg");
+
+ // same object -> returns true
+ assertTrue(editTut01Command.equals(editTut01Command));
+
+ // same values -> returns true
+ EditGroupCommand editTut01CommandCopy = new EditGroupCommand(tut01, "https://t.me/abcdefg");
+ assertTrue(editTut01Command.equals(editTut01CommandCopy));
+
+ // different types -> returns false
+ assertFalse(editTut01Command.equals(1));
+
+ // null -> returns false
+ assertFalse(editTut01Command.equals(null));
+
+ // different person -> returns false
+ assertFalse(editTut01Command.equals(editLab01Command));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Group group = new Group("LAB10");
+ String link = "https://t.me/invite";
+ EditGroupCommand command = new EditGroupCommand(group, link);
+
+ String expectedString = EditGroupCommand.class.getCanonicalName() + "{toEdit=" + LAB10 + "}";
+ assertEquals(expectedString, command.toString());
+ }
+
+ /**
+ * A default model stub that have all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteGroup(Group target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGroup(Group target, Group editedGroup) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single person.
+ */
+ private class ModelStubWithGroup extends ModelStub {
+ private final Group group;
+
+ ModelStubWithGroup(Group group) {
+ requireNonNull(group);
+ this.group = group;
+ }
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return this.group.isSameGroup(group);
+ }
+ }
+
+ /**
+ * A Model stub that always accept the person being added.
+ */
+ private class ModelStubAcceptingGroupAdded extends ModelStub {
+ final ArrayList groupsAdded = new ArrayList<>();
+
+ @Override
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return groupsAdded.stream().anyMatch(group::isSameGroup);
+ }
+
+ @Override
+ public void addGroup(Group group) {
+ requireNonNull(group);
+ groupsAdded.add(group);
+ }
+
+ @Override
+ public void setGroup(Group target, Group group) {
+ requireNonNull(group);
+ int index = groupsAdded.indexOf(target);
+ groupsAdded.set(index, group);
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ return new AddressBook();
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..a43224714a4 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -5,11 +5,14 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_REMARK_OUTSPOKEN;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_YEAR_AMY;
import org.junit.jupiter.api.Test;
@@ -44,16 +47,28 @@ public void equals() {
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
+ // different year -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withYear(VALID_YEAR_AMY).build();
+ assertFalse(DESC_AMY.equals(editedAmy));
+
+ // different telegram -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTelegram(VALID_TELEGRAM_AMY).build();
+ assertFalse(DESC_AMY.equals(editedAmy));
+
// different email -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different address -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
+ // different major -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withMajor(VALID_MAJOR_BOB).build();
+ assertFalse(DESC_AMY.equals(editedAmy));
+
+ // different remark -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withRemark(VALID_REMARK_OUTSPOKEN).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different tags -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build();
+ // different groups -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withGroups(VALID_GROUP_LAB).build();
assertFalse(DESC_AMY.equals(editedAmy));
}
@@ -63,9 +78,12 @@ public void toStringMethod() {
String expected = EditPersonDescriptor.class.getCanonicalName() + "{name="
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
- + editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
- + editPersonDescriptor.getTags().orElse(null) + "}";
+ + editPersonDescriptor.getEmail().orElse(null) + ", year="
+ + editPersonDescriptor.getYear().orElse(null) + ", major="
+ + editPersonDescriptor.getMajor().orElse(null) + ", telegram="
+ + editPersonDescriptor.getTelegram().orElse(null) + ", remark="
+ + editPersonDescriptor.getRemark().orElse(null) + ", groups="
+ + editPersonDescriptor.getGroups().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
index 9533c473875..f5854e5dfa3 100644
--- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
@@ -14,7 +14,7 @@ public class ExitCommandTest {
@Test
public void execute_exit_success() {
- CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true);
assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/FilterCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
new file mode 100644
index 00000000000..645e0616bf8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
@@ -0,0 +1,91 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code FilterCommand}.
+ */
+public class FilterCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ GroupContainsKeywordsPredicate firstPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("TUT01"));
+ GroupContainsKeywordsPredicate secondPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("TUT02"));
+
+ FilterCommand findFirstCommand = new FilterCommand(firstPredicate);
+ FilterCommand findSecondCommand = new FilterCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ FilterCommand findFirstCommandCopy = new FilterCommand(firstPredicate);
+ assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(findFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(findFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(findFirstCommand.equals(findSecondCommand));
+ }
+
+ @Test
+ public void execute_zeroKeywords_noPersonFound() {
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ GroupContainsKeywordsPredicate predicate = preparePredicate(" ");
+ FilterCommand command = new FilterCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_multipleKeywords_multiplePersonsFound() {
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2);
+ GroupContainsKeywordsPredicate predicate = preparePredicate("LAB10");
+ FilterCommand command = new FilterCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ALICE, BENSON), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void toStringMethod() {
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT10"));
+ FilterCommand filterCommand = new FilterCommand(predicate);
+ String expected = FilterCommand.class.getCanonicalName() + "{group=" + predicate + "}";
+ assertEquals(expected, filterCommand.toString());
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code GroupContainsKeywordsPredicate}.
+ */
+ private GroupContainsKeywordsPredicate preparePredicate(String userInput) {
+ return new GroupContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..6cd744887b1 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -5,10 +5,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import java.util.Arrays;
import java.util.Collections;
diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
index 4904fc4352e..3b35c387c4c 100644
--- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
@@ -14,7 +14,7 @@ public class HelpCommandTest {
@Test
public void execute_help_success() {
- CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false, false);
assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..12d9e84b7fb 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -2,8 +2,8 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/seedu/address/logic/commands/MailCommandTest.java b/src/test/java/seedu/address/logic/commands/MailCommandTest.java
new file mode 100644
index 00000000000..cd6dfe7773e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/MailCommandTest.java
@@ -0,0 +1,87 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+public class MailCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ GroupContainsKeywordsPredicate firstPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("TUT01"));
+ GroupContainsKeywordsPredicate secondPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("TUT02"));
+
+ MailCommand findFirstCommand = new MailCommand(firstPredicate);
+ MailCommand findSecondCommand = new MailCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ MailCommand findFirstCommandCopy = new MailCommand(firstPredicate);
+ assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(findFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(findFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(findFirstCommand.equals(findSecondCommand));
+ }
+
+ @Test
+ public void execute_noPredicate_success() {
+ Model model = new ModelManager();
+ MailCommand mailCommand = new MailCommand();
+ CommandResult commandResult = mailCommand.execute(model);
+ assertEquals(MailCommand.SHOW_MAILTO_LINK, commandResult.getFeedbackToUser());
+ }
+
+
+ @Test
+ public void execute_withMultiplePredicate_success() {
+ Model model = new ModelManager();
+ List personList = Arrays.asList(
+ new PersonBuilder().withName("Alice").withEmail("test1@example.com").build(),
+ new PersonBuilder().withName("Bob").withEmail("test2@example.com").build()
+ );
+ model.addPerson(personList.get(0));
+ model.addPerson(personList.get(1));
+
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT01"));
+ model.updateFilteredPersonList(predicate);
+
+ MailCommand mailCommand = new MailCommand(predicate);
+ CommandResult commandResult = mailCommand.execute(model);
+
+ // Extract emails from personList filtered by the predicate
+ List emails = personList.stream()
+ .filter(person -> predicate.test(person))
+ .map(person -> person.getEmail().toString())
+ .collect(Collectors.toList());
+
+ assertEquals(MailCommand.SHOW_MAILTO_LINK, commandResult.getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/MailTelegramCommandTest.java b/src/test/java/seedu/address/logic/commands/MailTelegramCommandTest.java
new file mode 100644
index 00000000000..7bee5e47efb
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/MailTelegramCommandTest.java
@@ -0,0 +1,78 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+public class MailTelegramCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ Group group1 = new Group("TUT01");
+ Group group2 = new Group("TUT02");
+
+ MailTelegramCommand findFirstCommand = new MailTelegramCommand(group1);
+ MailTelegramCommand findSecondCommand = new MailTelegramCommand(group2);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ MailTelegramCommand findFirstCommandCopy = new MailTelegramCommand(group1);
+ assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(findFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(findFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(findFirstCommand.equals(findSecondCommand));
+ }
+
+ @Test
+ public void execute_withMultiplePredicate_success() {
+ Model model = new ModelManager();
+ List personList = Arrays.asList(
+ new PersonBuilder().withName("Alice").withEmail("test1@example.com").build(),
+ new PersonBuilder().withName("Bob").withEmail("test2@example.com").build()
+ );
+ model.addPerson(personList.get(0));
+ model.addPerson(personList.get(1));
+
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT01"));
+ Group group = new Group("TUT01");
+
+ model.updateFilteredPersonList(predicate);
+
+ MailTelegramCommand mailCommand = new MailTelegramCommand(group);
+ CommandResult commandResult = mailCommand.execute(model);
+
+ // Extract emails from personList filtered by the predicate
+ List emails = personList.stream()
+ .filter(person -> predicate.test(person))
+ .map(person -> person.getEmail().toString())
+ .collect(Collectors.toList());
+
+ assertEquals(MailTelegramCommand.SHOW_MAILTO_LINK, commandResult.getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/MarkAttendanceCommandTest.java b/src/test/java/seedu/address/logic/commands/MarkAttendanceCommandTest.java
new file mode 100644
index 00000000000..4f8f7dc81a1
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/MarkAttendanceCommandTest.java
@@ -0,0 +1,79 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.LAB10;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+public class MarkAttendanceCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void constructor_nullMarkAttendanceConstructor_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new MarkAttendanceCommand(null,
+ LAB10, 1, null));
+ assertThrows(NullPointerException.class, () -> new MarkAttendanceCommand(INDEX_FIRST_PERSON,
+ null, 1, null));
+ }
+
+ @Test
+ public void execute_attendanceIsMarked_markSuccessful() throws Exception {
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ firstPerson.getMatchingGroup(new Group("TUT04")).markAttendance(1, "A");
+
+ MarkAttendanceCommand markCommand = new MarkAttendanceCommand(INDEX_FIRST_PERSON,
+ new Group("TUT04"), 1, "A");
+
+ assertCommandSuccess(markCommand, model, MarkAttendanceCommand.MESSAGE_SUCCESS, expectedModel);
+ }
+
+ @Test
+ public void equals() {
+ Group tut01 = new Group("TUT01");
+ Group lab01 = new Group("LAB01");
+ MarkAttendanceCommand markAttendance1 = new MarkAttendanceCommand(INDEX_FIRST_PERSON,
+ tut01, 1, "A");
+ MarkAttendanceCommand markAttendance2 = new MarkAttendanceCommand(INDEX_FIRST_PERSON,
+ lab01, 1, "A");
+
+ // same object -> returns true
+ assertTrue(markAttendance1.equals(markAttendance1));
+
+ // same values -> returns true
+ MarkAttendanceCommand markAttendance1Copy = new MarkAttendanceCommand(INDEX_FIRST_PERSON,
+ tut01, 1, "A");
+ assertTrue(markAttendance1.equals(markAttendance1Copy));
+
+ // different types -> returns false
+ assertFalse(markAttendance1.equals(1));
+
+ // null -> returns false
+ assertFalse(markAttendance1.equals(null));
+
+ // different person -> returns false
+ assertFalse(markAttendance1.equals(markAttendance2));
+ }
+
+ @Test
+ public void toStringMethod() {
+ AddGroupCommand addGroupCommand = new AddGroupCommand(LAB10);
+ String expected = AddGroupCommand.class.getCanonicalName() + "{toAdd=" + LAB10 + "}";
+ assertEquals(expected, addGroupCommand.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..4ad276743d9 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -1,33 +1,47 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.GROUP_DESC_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.GROUP_DESC_TUTORIAL;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_GROUP_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_MAJOR_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TELEGRAM_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_YEAR_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.MAJOR_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.MAJOR_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.REMARK_DESC_OUTSPOKEN;
+import static seedu.address.logic.commands.CommandTestUtil.REMARK_DESC_SHY;
+import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_REMARK_OUTSPOKEN;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_YEAR_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.YEAR_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.YEAR_DESC_BOB;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -37,12 +51,14 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
import seedu.address.testutil.PersonBuilder;
public class AddCommandParserTest {
@@ -50,25 +66,31 @@ public class AddCommandParserTest {
@Test
public void parse_allFieldsPresent_success() {
- Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build();
+ Person expectedPerson = new PersonBuilder(BOB).withRemark(VALID_REMARK_OUTSPOKEN)
+ .withGroups(VALID_GROUP_TUTORIAL)
+ .build();
// whitespace only preamble
assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ + YEAR_DESC_BOB + MAJOR_DESC_BOB + TELEGRAM_DESC_BOB
+ + REMARK_DESC_OUTSPOKEN + GROUP_DESC_TUTORIAL,
+ new AddCommand(expectedPerson));
- // multiple tags - all accepted
- Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
+ // multiple groups - all accepted
+ Person expectedPersonMultipleGroups = new PersonBuilder(BOB).withRemark(VALID_REMARK_OUTSPOKEN).withGroups(
+ VALID_GROUP_TUTORIAL, VALID_GROUP_LAB)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
- new AddCommand(expectedPersonMultipleTags));
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + YEAR_DESC_BOB + MAJOR_DESC_BOB
+ + TELEGRAM_DESC_BOB + REMARK_DESC_OUTSPOKEN + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL,
+ new AddCommand(expectedPersonMultipleGroups));
}
@Test
- public void parse_repeatedNonTagValue_failure() {
- String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ public void parse_repeatedNonGroupValue_failure() {
+ String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + YEAR_DESC_BOB
+ + TELEGRAM_DESC_BOB + MAJOR_DESC_BOB + REMARK_DESC_OUTSPOKEN + GROUP_DESC_TUTORIAL;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -82,15 +104,29 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, EMAIL_DESC_AMY + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
- // multiple addresses
- assertParseFailure(parser, ADDRESS_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // multiple years
+ assertParseFailure(parser, YEAR_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_YEAR));
+
+ // multiple majors
+ assertParseFailure(parser, MAJOR_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_MAJOR));
+
+ // multiple telegrams
+ assertParseFailure(parser, TELEGRAM_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_TELEGRAM));
+
+ // multiple remarks
+ assertParseFailure(parser, REMARK_DESC_SHY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_REMARK));
+
// multiple fields repeated
assertParseFailure(parser,
- validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
+ validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + MAJOR_DESC_AMY
+ validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_MAJOR, PREFIX_EMAIL, PREFIX_YEAR,
+ PREFIX_TELEGRAM, PREFIX_REMARK, PREFIX_PHONE));
// invalid value followed by valid value
@@ -102,13 +138,22 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, INVALID_EMAIL_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ // invalid year
+ assertParseFailure(parser, INVALID_YEAR_DESC + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_YEAR));
+
+ // invalid telegram
+ assertParseFailure(parser, INVALID_TELEGRAM_DESC + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_TELEGRAM));
+
// invalid phone
assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // invalid major
+ assertParseFailure(parser, INVALID_MAJOR_DESC + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_MAJOR));
+
// valid value followed by invalid value
@@ -120,21 +165,30 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ // invalid year
+ assertParseFailure(parser, validExpectedPersonString + INVALID_YEAR_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_YEAR));
+
+ // invalid telegram
+ assertParseFailure(parser, validExpectedPersonString + INVALID_TELEGRAM_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_TELEGRAM));
+
// invalid phone
assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // invalid major
+ assertParseFailure(parser, validExpectedPersonString + INVALID_MAJOR_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_MAJOR));
+
}
@Test
public void parse_optionalFieldsMissing_success() {
- // zero tags
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
- new AddCommand(expectedPerson));
+ // no remark and zero groups
+ Person expectedPerson = new PersonBuilder(AMY).withRemark("").withGroups().build();
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + YEAR_DESC_AMY
+ + TELEGRAM_DESC_AMY + MAJOR_DESC_AMY, new AddCommand(expectedPerson));
}
@Test
@@ -142,55 +196,75 @@ public void parse_compulsoryFieldMissing_failure() {
String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE);
// missing name prefix
- assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
+ assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + MAJOR_DESC_BOB, expectedMessage);
// missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
+ assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + MAJOR_DESC_BOB, expectedMessage);
// missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + MAJOR_DESC_BOB, expectedMessage);
+
+ // missing year prefix
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB
+ + VALID_YEAR_BOB + TELEGRAM_DESC_BOB + MAJOR_DESC_BOB, expectedMessage);
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ // missing telegram prefix
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB
+ + YEAR_DESC_BOB + VALID_TELEGRAM_BOB + MAJOR_DESC_BOB, expectedMessage);
+
+ // missing major prefix
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + YEAR_DESC_BOB + VALID_TELEGRAM_BOB + VALID_MAJOR_BOB, expectedMessage);
// all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB
+ + VALID_YEAR_BOB + VALID_TELEGRAM_BOB + VALID_MAJOR_BOB, expectedMessage);
}
@Test
public void parse_invalidValue_failure() {
// invalid name
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + MAJOR_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL, Name.MESSAGE_CONSTRAINTS);
// invalid phone
- assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + MAJOR_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL, Phone.MESSAGE_CONSTRAINTS);
// invalid email
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + MAJOR_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL, Email.MESSAGE_CONSTRAINTS);
+
+ // invalid year
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + MAJOR_DESC_BOB
+ + INVALID_YEAR_DESC + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL,
+ Year.MESSAGE_CONSTRAINTS);
- // invalid address
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ // invalid telegram
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + MAJOR_DESC_BOB
+ + YEAR_DESC_BOB + INVALID_TELEGRAM_DESC + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL,
+ Telegram.MESSAGE_CONSTRAINTS);
- // invalid tag
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+ // invalid major
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_MAJOR_DESC
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL, Major.MESSAGE_CONSTRAINTS);
+
+ // invalid group
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + MAJOR_DESC_BOB
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB + INVALID_GROUP_DESC + VALID_GROUP_TUTORIAL,
+ Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_MAJOR_DESC
+ + YEAR_DESC_BOB + TELEGRAM_DESC_BOB, Name.MESSAGE_CONSTRAINTS);
// non-empty preamble
assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ + MAJOR_DESC_BOB + TELEGRAM_DESC_BOB + GROUP_DESC_LAB + GROUP_DESC_TUTORIAL,
+
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddGroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddGroupCommandParserTest.java
new file mode 100644
index 00000000000..abc4f979d59
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddGroupCommandParserTest.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.AddGroupCommand;
+import seedu.address.model.group.Group;
+
+public class AddGroupCommandParserTest {
+ private AddGroupCommandParser parser = new AddGroupCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsAddGroupCommand() {
+ assertParseSuccess(parser, " g/TUT04", new AddGroupCommand(new Group(VALID_GROUP_TUTORIAL)));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, " a",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..625c23896bd 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -14,15 +14,22 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddGroupCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteGroupCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.EditGroupCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.MailCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -62,12 +69,42 @@ public void parseCommand_edit() throws Exception {
assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
}
+ @Test
+ public void parseCommand_addGroup() throws Exception {
+ AddGroupCommand command = (AddGroupCommand) parser.parseCommand(AddGroupCommand.COMMAND_WORD + " "
+ + "g/TUT01");
+ assertEquals(new AddGroupCommand(new Group("TUT01")), command);
+ }
+
+ @Test
+ public void parseCommand_deleteGroup() throws Exception {
+ DeleteGroupCommand command = (DeleteGroupCommand) parser.parseCommand(DeleteGroupCommand.COMMAND_WORD + " "
+ + "g/TUT01");
+ assertEquals(new DeleteGroupCommand(new Group("TUT01")), command);
+ }
+
+ @Test
+ public void parseCommand_editGroup() throws Exception {
+ EditGroupCommand command = (EditGroupCommand) parser.parseCommand(EditGroupCommand.COMMAND_WORD + " "
+ + "g/TUT01 tg/https://t.me/abcdefg");
+ assertEquals(new EditGroupCommand(new Group("TUT01"), "https://t.me/abcdefg"), command);
+ }
+
@Test
public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand);
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand);
}
+
+ @Test
+ public void parseCommand_filter() throws Exception {
+ List keywords = Arrays.asList("TUT01", "LAB02", "REC03");
+ FilterCommand command = (FilterCommand) parser.parseCommand(
+ FilterCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
+ assertEquals(new FilterCommand(new GroupContainsKeywordsPredicate(keywords)), command);
+ }
+
@Test
public void parseCommand_find() throws Exception {
List keywords = Arrays.asList("foo", "bar", "baz");
@@ -88,6 +125,14 @@ public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_mail() throws Exception {
+ List keywords = Arrays.asList("TUT01", "LAB02", "REC03");
+ MailCommand command = (MailCommand) parser.parseCommand(
+ MailCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
+ assertEquals(new MailCommand(new GroupContainsKeywordsPredicate(keywords)), command);
+ }
+
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
diff --git a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
index c97308935f5..0d7355ce351 100644
--- a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
+++ b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java
@@ -55,7 +55,7 @@ private void assertArgumentAbsent(ArgumentMultimap argMultimap, Prefix prefix) {
@Test
public void tokenize_noPrefixes_allTakenAsPreamble() {
- String argsString = " some random string /t tag with leading and trailing spaces ";
+ String argsString = " some random string /g group with leading and trailing spaces ";
ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString);
// Same string expected as preamble, but leading/trailing spaces should be trimmed
diff --git a/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.java
new file mode 100644
index 00000000000..9cdc0ac2009
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.DeleteGroupCommand;
+import seedu.address.model.group.Group;
+
+public class DeleteGroupCommandParserTest {
+ private DeleteGroupCommandParser parser = new DeleteGroupCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsDeleteGroupCommand() {
+ assertParseSuccess(parser, " g/TUT04", new DeleteGroupCommand(new Group(VALID_GROUP_TUTORIAL)));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, " a",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..07e281f0248 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -1,31 +1,35 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.GROUP_DESC_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.GROUP_DESC_TUTORIAL;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_GROUP_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_MAJOR_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_YEAR_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.MAJOR_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.MAJOR_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_YEAR_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.YEAR_DESC_AMY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
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_YEAR;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -38,16 +42,16 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
public class EditCommandParserTest {
- private static final String TAG_EMPTY = " " + PREFIX_TAG;
+ private static final String GROUP_EMPTY = " " + PREFIX_GROUP;
private static final String MESSAGE_INVALID_FORMAT =
String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE);
@@ -86,32 +90,36 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email
- assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address
- assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
+ assertParseFailure(parser, "1" + INVALID_MAJOR_DESC, Major.MESSAGE_CONSTRAINTS); // invalid major
+ assertParseFailure(parser, "1" + INVALID_GROUP_DESC,
+ Group.MESSAGE_GROUP_NAME_CONSTRAINTS); // invalid group
// invalid phone followed by valid email
assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
- // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
- // parsing it together with a valid tag results in error
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+ // while parsing {@code PREFIX_GROUP} alone will reset the group of the {@code Person} being edited,
+ // parsing it together with a valid group results in error
+ assertParseFailure(parser, "1" + GROUP_DESC_TUTORIAL + GROUP_DESC_LAB + GROUP_EMPTY,
+ Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ assertParseFailure(parser, "1" + GROUP_DESC_TUTORIAL + GROUP_EMPTY + GROUP_DESC_LAB,
+ Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ assertParseFailure(parser, "1" + GROUP_EMPTY + GROUP_DESC_TUTORIAL + GROUP_DESC_LAB,
+ Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
// multiple invalid values, but only the first invalid value is captured
- assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
+ assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_MAJOR_AMY + VALID_PHONE_AMY,
Name.MESSAGE_CONSTRAINTS);
}
@Test
public void parse_allFieldsSpecified_success() {
Index targetIndex = INDEX_SECOND_PERSON;
- String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND;
+ String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + GROUP_DESC_LAB
+ + EMAIL_DESC_AMY + MAJOR_DESC_AMY + NAME_DESC_AMY + GROUP_DESC_TUTORIAL;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withMajor(VALID_MAJOR_AMY)
+ .withGroups(VALID_GROUP_LAB, VALID_GROUP_TUTORIAL).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
@@ -150,15 +158,21 @@ public void parse_oneFieldSpecified_success() {
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // address
- userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY;
- descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build();
+ // year
+ userInput = targetIndex.getOneBased() + YEAR_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withYear(VALID_YEAR_AMY).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // tags
- userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND;
- descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
+ // major
+ userInput = targetIndex.getOneBased() + MAJOR_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withMajor(VALID_MAJOR_AMY).build();
+ expectedCommand = new EditCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // groups
+ userInput = targetIndex.getOneBased() + GROUP_DESC_TUTORIAL;
+ descriptor = new EditPersonDescriptorBuilder().withGroups(VALID_GROUP_TUTORIAL).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -166,7 +180,7 @@ public void parse_oneFieldSpecified_success() {
@Test
public void parse_multipleRepeatedFields_failure() {
// More extensive testing of duplicate parameter detections is done in
- // AddCommandParserTest#parse_repeatedNonTagValue_failure()
+ // AddCommandParserTest#parse_repeatedNonGroupValue_failure()
// valid followed by invalid
Index targetIndex = INDEX_FIRST_PERSON;
@@ -180,27 +194,28 @@ public void parse_multipleRepeatedFields_failure() {
assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
// mulltiple valid fields repeated
- userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY
- + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND
- + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND;
+ userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + MAJOR_DESC_AMY + EMAIL_DESC_AMY
+ + GROUP_DESC_TUTORIAL + PHONE_DESC_AMY + MAJOR_DESC_AMY + EMAIL_DESC_AMY + GROUP_DESC_TUTORIAL
+ + PHONE_DESC_BOB + MAJOR_DESC_BOB + EMAIL_DESC_BOB + GROUP_DESC_LAB;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_MAJOR));
// multiple invalid values
- userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC
- + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC;
+ userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_MAJOR_DESC + INVALID_EMAIL_DESC
+ + INVALID_YEAR_DESC + INVALID_PHONE_DESC + INVALID_MAJOR_DESC + INVALID_EMAIL_DESC
+ + INVALID_YEAR_DESC;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_MAJOR, PREFIX_YEAR));
}
@Test
- public void parse_resetTags_success() {
+ public void parse_resetGroups_success() {
Index targetIndex = INDEX_THIRD_PERSON;
- String userInput = targetIndex.getOneBased() + TAG_EMPTY;
+ String userInput = targetIndex.getOneBased() + GROUP_EMPTY;
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withGroups().build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
diff --git a/src/test/java/seedu/address/logic/parser/EditGroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditGroupCommandParserTest.java
new file mode 100644
index 00000000000..54659705e9f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/EditGroupCommandParserTest.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_LINK;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.EditGroupCommand;
+import seedu.address.model.group.Group;
+
+public class EditGroupCommandParserTest {
+ private EditGroupCommandParser parser = new EditGroupCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsEditGroupCommand() {
+ assertParseSuccess(parser, " g/TUT04 tg/https://t.me/abcdefg",
+ new EditGroupCommand(new Group(VALID_GROUP_TUTORIAL), VALID_TELEGRAM_LINK));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, " g/abcd tg/https://t.me/abcdef", Group.MESSAGE_GROUP_NAME_CONSTRAINTS);
+ assertParseFailure(parser, " g/TUT01 tg/www.google.com", Group.MESSAGE_LINK_CONSTRAINTS);
+ assertParseFailure(parser, " a",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGroupCommand.MESSAGE_USAGE));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
new file mode 100644
index 00000000000..c58c3c37cc9
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+public class FilterCommandParserTest {
+
+ private FilterCommandParser parser = new FilterCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+
+
+ @Test
+ public void parse_invalidArg_throwsParseException() {
+ assertParseFailure(parser, "TUT", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Group.MESSAGE_CONSTRAINTS_KEYWORD));
+ }
+
+ @Test
+ public void parse_validArgs_returnsFilterCommand() {
+ // no leading and trailing whitespaces
+ FilterCommand expectedFilterCommand =
+ new FilterCommand(new GroupContainsKeywordsPredicate(Arrays.asList("TUT10", "LAB05")));
+ assertParseSuccess(parser, "TUT10 LAB05", expectedFilterCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n TUT10 \n \t LAB05 \t", expectedFilterCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..6f4286a2442 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -30,5 +30,4 @@ public void parse_validArgs_returnsFindCommand() {
// multiple whitespaces between keywords
assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand);
}
-
}
diff --git a/src/test/java/seedu/address/logic/parser/MailCommandParserTest.java b/src/test/java/seedu/address/logic/parser/MailCommandParserTest.java
new file mode 100644
index 00000000000..5b759f33f5e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/MailCommandParserTest.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.MailCommand;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+public class MailCommandParserTest {
+
+ private MailCommandParser parser = new MailCommandParser();
+
+ @Test
+ public void parse_invalidArg_throwsParseException() {
+ assertParseFailure(parser, "TUT", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Group.MESSAGE_CONSTRAINTS_KEYWORD));
+ }
+
+ @Test
+ public void parse_validArgs_returnsFilterCommand() {
+ // no leading and trailing whitespaces
+ MailCommand expectedMailCommand =
+ new MailCommand(new GroupContainsKeywordsPredicate(Arrays.asList("TUT10", "LAB05")));
+ assertParseSuccess(parser, "TUT10 LAB05", expectedMailCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n TUT10 \n \t LAB05 \t", expectedMailCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..cbe967f770f 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -14,25 +14,32 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
public class ParserUtilTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
- private static final String INVALID_ADDRESS = " ";
+ private static final String INVALID_MAJOR = " ";
private static final String INVALID_EMAIL = "example.com";
- private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_YEAR = "7";
+ private static final String INVALID_TELEGRAM = "inval!d";
+ private static final String INVALID_GROUP = "TUT93828";
private static final String VALID_NAME = "Rachel Walker";
private static final String VALID_PHONE = "123456";
- private static final String VALID_ADDRESS = "123 Main Street #0505";
+ private static final String VALID_MAJOR = "Computer Science";
private static final String VALID_EMAIL = "rachel@example.com";
- private static final String VALID_TAG_1 = "friend";
- private static final String VALID_TAG_2 = "neighbour";
+ private static final String VALID_YEAR = "7";
+ private static final String VALID_TELEGRAM = "telegram";
+ private static final String VALID_REMARK = "shy";
+ private static final String VALID_GROUP_1 = "TUT04";
+ private static final String VALID_GROUP_2 = "LAB10";
private static final String WHITESPACE = " \t\r\n";
@@ -103,26 +110,26 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc
}
@Test
- public void parseAddress_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
+ public void parseMajor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseMajor((String) null));
}
@Test
- public void parseAddress_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS));
+ public void parseMajor_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseMajor(INVALID_MAJOR));
}
@Test
- public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception {
- Address expectedAddress = new Address(VALID_ADDRESS);
- assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS));
+ public void parseMajor_validValueWithoutWhitespace_returnsMajor() throws Exception {
+ Major expectedMajor = new Major(VALID_MAJOR);
+ assertEquals(expectedMajor, ParserUtil.parseMajor(VALID_MAJOR));
}
@Test
- public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws Exception {
- String addressWithWhitespace = WHITESPACE + VALID_ADDRESS + WHITESPACE;
- Address expectedAddress = new Address(VALID_ADDRESS);
- assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace));
+ public void parseMajor_validValueWithWhitespace_returnsTrimmedMajor() throws Exception {
+ String majorWithWhitespace = WHITESPACE + VALID_MAJOR + WHITESPACE;
+ Major expectedMajor = new Major(VALID_MAJOR);
+ assertEquals(expectedMajor, ParserUtil.parseMajor(majorWithWhitespace));
}
@Test
@@ -149,48 +156,100 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc
}
@Test
- public void parseTag_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null));
+ public void parseYear_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseYear(INVALID_YEAR));
}
@Test
- public void parseTag_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG));
+ public void parseYear_null_throwsNullPointerException() throws Exception {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseYear(null));
}
@Test
- public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception {
- Tag expectedTag = new Tag(VALID_TAG_1);
- assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1));
+ public void parseTelegram_invalidTelegram_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseTelegram(INVALID_TELEGRAM));
}
@Test
- public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception {
- String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE;
- Tag expectedTag = new Tag(VALID_TAG_1);
- assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace));
+ public void parseTelegram_null_throwsNullPointerException() throws Exception {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseTelegram(null));
}
@Test
- public void parseTags_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null));
+ public void parseTelegram_validValueWithoutWhitespace_returnsTelegram() throws Exception {
+ Telegram expectedTelegram = new Telegram(VALID_TELEGRAM);
+ assertEquals(expectedTelegram, ParserUtil.parseTelegram(VALID_TELEGRAM));
}
@Test
- public void parseTags_collectionWithInvalidTags_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)));
+ public void parseTelegram_validValueWithWhitespace_returnsTrimmedTelegram() throws Exception {
+ String telegramWithWhitespace = WHITESPACE + VALID_TELEGRAM + WHITESPACE;
+ Telegram expectedTelegram = new Telegram(VALID_TELEGRAM);
+ assertEquals(expectedTelegram, ParserUtil.parseTelegram(telegramWithWhitespace));
}
@Test
- public void parseTags_emptyCollection_returnsEmptySet() throws Exception {
- assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty());
+ public void parseRemark_null_throwsNullPointerException() throws Exception {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseRemark(null));
}
@Test
- public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
- Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2));
- Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2)));
+ public void parseRemark_validValueWithoutWhitespace_returnsRemark() throws Exception {
+ Remark expectedRemark = new Remark(VALID_REMARK);
+ assertEquals(expectedRemark, ParserUtil.parseRemark(VALID_REMARK));
+ }
+
+ @Test
+ public void parseRemark_validValueWithWhitespace_returnsTrimmedRemark() throws Exception {
+ String remarkWithWhitespace = WHITESPACE + VALID_REMARK + WHITESPACE;
+ Remark expectedRemark = new Remark(VALID_REMARK);
+ assertEquals(expectedRemark, ParserUtil.parseRemark(remarkWithWhitespace));
+ }
+
+ @Test
+ public void parseGroup_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseGroup(null));
+ }
+
+ @Test
+ public void parseGroup_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseGroup(INVALID_GROUP));
+ }
+
+ @Test
+ public void parseGroup_validValueWithoutWhitespace_returnsGroup() throws Exception {
+ Group expectedGroup = new Group(VALID_GROUP_1);
+ assertEquals(expectedGroup, ParserUtil.parseGroup(VALID_GROUP_1));
+ }
+
+ @Test
+ public void parseGroup_validValueWithWhitespace_returnsTrimmedGroup() throws Exception {
+ String groupWithWhitespace = WHITESPACE + VALID_GROUP_1 + WHITESPACE;
+ Group expectedGroup = new Group(VALID_GROUP_1);
+ assertEquals(expectedGroup, ParserUtil.parseGroup(groupWithWhitespace));
+ }
+
+ @Test
+ public void parseGroups_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseGroups(null));
+ }
+
+ @Test
+ public void parseGroups_collectionWithInvalidGroups_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseGroups(Arrays.asList(VALID_GROUP_1, INVALID_GROUP)));
+ }
+
+ @Test
+ public void parseGroups_emptyCollection_returnsEmptySet() throws Exception {
+ assertTrue(ParserUtil.parseGroups(Collections.emptyList()).isEmpty());
+ }
+
+ @Test
+ public void parseGroups_collectionWithValidGroups_returnsGroupSet() throws Exception {
+ Set actualGroupSet = ParserUtil.parseGroups(Arrays.asList(VALID_GROUP_1, VALID_GROUP_2));
+ Set expectedGroupSet = new HashSet(Arrays.asList(new Group(VALID_GROUP_1),
+ new Group(VALID_GROUP_2)));
- assertEquals(expectedTagSet, actualTagSet);
+ assertEquals(expectedGroupSet, actualGroupSet);
}
}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..87ceaf5beb1 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -3,12 +3,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalPersons.ALICE;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -18,6 +20,9 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.exceptions.DuplicateGroupException;
+import seedu.address.model.group.exceptions.GroupNotFoundException;
import seedu.address.model.person.Person;
import seedu.address.model.person.exceptions.DuplicatePersonException;
import seedu.address.testutil.PersonBuilder;
@@ -46,10 +51,11 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
@Test
public void resetData_withDuplicatePersons_throwsDuplicatePersonException() {
// Two persons with the same identity fields
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).withGroups(VALID_GROUP_LAB)
.build();
List newPersons = Arrays.asList(ALICE, editedAlice);
- AddressBookStub newData = new AddressBookStub(newPersons);
+ List newGroups = Arrays.asList(new Group(VALID_GROUP_TUTORIAL), new Group(VALID_GROUP_LAB));
+ AddressBookStub newData = new AddressBookStub(newPersons, newGroups);
assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData));
}
@@ -73,7 +79,7 @@ public void hasPerson_personInAddressBook_returnsTrue() {
@Test
public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() {
addressBook.addPerson(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).withGroups(VALID_GROUP_LAB)
.build();
assertTrue(addressBook.hasPerson(editedAlice));
}
@@ -83,26 +89,226 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0));
}
+ @Test
+ public void resetData_withDuplicateGroups_throwsDuplicatePersonException() {
+ // Two persons with the same identity fields
+ List newPersons = Arrays.asList(ALICE);
+ List newGroups = Arrays.asList(new Group(VALID_GROUP_TUTORIAL), new Group(VALID_GROUP_TUTORIAL));
+ AddressBookStub newData = new AddressBookStub(newPersons, newGroups);
+
+ assertThrows(DuplicateGroupException.class, () -> addressBook.resetData(newData));
+ }
+
+ @Test
+ public void hasGroup_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> addressBook.hasGroup(null));
+ }
+
+ @Test
+ public void hasGroup_groupNotInAddressBook_returnsFalse() {
+ assertFalse(addressBook.hasGroup(new Group(VALID_GROUP_LAB)));
+ }
+
+ @Test
+ public void hasGroup_groupInAddressBook_returnsTrue() {
+ addressBook.addGroup(new Group(VALID_GROUP_LAB));
+ assertTrue(addressBook.hasGroup(new Group(VALID_GROUP_LAB)));
+ }
+
+ @Test
+ public void hasGroup_groupWithSameIdentityFieldsInAddressBook_returnsTrue() {
+ addressBook.addGroup(new Group(VALID_GROUP_LAB));
+ assertTrue(addressBook.hasGroup(new Group(VALID_GROUP_LAB)));
+ }
+
+ @Test
+ public void getGroupList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> addressBook.getGroupList().remove(0));
+ }
+
@Test
public void toStringMethod() {
- String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList() + "}";
+ String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList()
+ + ", groups=" + addressBook.getGroupList() + "}";
assertEquals(expected, addressBook.toString());
}
+ @Test
+ public void hashCode_equalAddressBooks_sameHashCode() {
+ // Create two identical address books
+ AddressBook addressBook1 = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+
+ // Add a person to the first address book
+ Person person1 = new PersonBuilder().withName("Alice").withGroups("TUT04").build();
+ addressBook1.addPerson(person1);
+
+ // Add a person to the second address book
+ Person person2 = new PersonBuilder().withName("Alice").withGroups("TUT04").build();
+ addressBook2.addPerson(person2);
+
+ // Check that the hash codes are equal
+ assertEquals(addressBook1.hashCode(), addressBook2.hashCode());
+ }
+
+ @Test
+ public void hashCode_differentAddressBooks_differentHashCodes() {
+ // Create two address books with different contents
+ AddressBook addressBook1 = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+
+ // Add a person to the first address book
+ Person person1 = new PersonBuilder().withName("Alice").withGroups("TUT05").build();
+ addressBook1.addPerson(person1);
+
+ // Add a person to the second address book
+ Person person2 = new PersonBuilder().withName("Bob").withGroups("LAB08").build();
+ addressBook2.addPerson(person2);
+
+ // Check that the hash codes are different
+ assertEquals(addressBook1.hashCode() != addressBook2.hashCode(), true);
+ }
+
+ @Test
+ public void removeGroup_existingGroup_success() {
+ // Create an address book with a group
+ AddressBook addressBook = new AddressBook();
+ Group groupToRemove = new Group("LAB08");
+ addressBook.addGroup(groupToRemove);
+
+ // Remove the group
+ addressBook.removeGroup(groupToRemove);
+
+ // Check that the group is no longer in the address book
+ assertFalse(addressBook.getGroupList().contains(groupToRemove));
+ }
+
+ @Test
+ public void removeGroup_nonExistingGroup_noChange() {
+ // Create an address book with a group
+ AddressBook addressBook = new AddressBook();
+ Group existingGroup = new Group("LAB05");
+ addressBook.addGroup(existingGroup);
+
+ // Create a group that does not exist in the address book
+ Group nonExistingGroup = new Group("LAB06");
+
+ // Throws GroupNotFoundException when the non-existing group is removed
+ assertThrows(GroupNotFoundException.class, () -> addressBook.removeGroup(nonExistingGroup));
+ }
+
+ @Test
+ public void setGroup_existingGroup_success() {
+ // Create an address book with an existing group
+ AddressBook addressBook = new AddressBook();
+ Group existingGroup = new Group("TUT04");
+ addressBook.addGroup(existingGroup);
+
+ // Create a new group
+ Group newGroup = new Group("TUT01");
+
+ // Replace the existing group with the new group
+ addressBook.setGroup(existingGroup, newGroup);
+
+ // Check that the new group is in the address book
+ assertFalse(addressBook.getGroupList().contains(existingGroup));
+ assertTrue(addressBook.getGroupList().contains(newGroup));
+ }
+
+ @Test
+ public void setGroup_nonExistingGroup_noChange() {
+ // Create an address book with an existing group
+ AddressBook addressBook = new AddressBook();
+ Group existingGroup = new Group("TUT04");
+ addressBook.addGroup(existingGroup);
+
+ // Create a new group
+ Group nonExistingGroup = new Group("TUT01");
+
+ // Throws GroupNotFoundException when the non-existing group is replaced
+ assertThrows(GroupNotFoundException.class, () -> addressBook.setGroup(nonExistingGroup, existingGroup));
+ }
+
+ @Test
+ public void equals_sameInstance_returnsTrue() {
+ AddressBook addressBook = new AddressBook();
+ assertTrue(addressBook.equals(addressBook));
+ }
+
+ @Test
+ public void equals_null_returnsFalse() {
+ AddressBook addressBook = new AddressBook();
+ assertFalse(addressBook.equals(null));
+ }
+
+ @Test
+ public void equals_differentClass_returnsFalse() {
+ AddressBook addressBook = new AddressBook();
+ assertFalse(addressBook.equals("Not an AddressBook instance"));
+ }
+
+ @Test
+ public void equals_differentPersons_returnsFalse() {
+ AddressBook addressBook1 = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+
+ // Add a person to the first address book
+ Person person1 = new PersonBuilder().withName("Alice").withGroups("TUT05").build();
+ addressBook1.addPerson(person1);
+
+ // Add a person to the second address book
+ Person person2 = new PersonBuilder().withName("Bob").withGroups("LAB08").build();
+ addressBook2.addPerson(person2);
+ assertFalse(addressBook1.equals(addressBook2));
+ }
+
+ @Test
+ public void equals_differentGroups_returnsFalse() {
+ AddressBook addressBook1 = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+ addressBook1.addGroup(new Group("TUT01"));
+ addressBook2.addGroup(new Group("LAB01"));
+ assertFalse(addressBook1.equals(addressBook2));
+ }
+
+ @Test
+ public void equals_sameContent_returnsTrue() {
+ AddressBook addressBook1 = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+ List persons = new ArrayList<>();
+ List groups = new ArrayList<>();
+ Person person = new PersonBuilder().withName("Alice").withGroups("TUT05").build();
+ addressBook1.addPerson(person);
+ groups.add(new Group("TUT05"));
+ addressBook1.setPersons(persons);
+ addressBook2.setPersons(persons);
+ addressBook1.setGroups(groups);
+ addressBook2.setGroups(groups);
+ assertTrue(addressBook1.equals(addressBook2));
+ }
+
/**
* A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
*/
private static class AddressBookStub implements ReadOnlyAddressBook {
private final ObservableList persons = FXCollections.observableArrayList();
+ private final ObservableList groups = FXCollections.observableArrayList();
- AddressBookStub(Collection persons) {
+ AddressBookStub(Collection persons, Collection groups) {
this.persons.setAll(persons);
+ this.groups.setAll(groups);
}
@Override
public ObservableList getPersonList() {
return persons;
}
+
+ @Override
+ public ObservableList getGroupList() {
+ return groups;
+ }
+
}
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..1341d2c252b 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.TUT04;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
@@ -88,6 +89,22 @@ public void hasPerson_personInAddressBook_returnsTrue() {
assertTrue(modelManager.hasPerson(ALICE));
}
+ @Test
+ public void hasGroup_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.hasGroup(null));
+ }
+
+ @Test
+ public void hasGroup_groupNotInAddressBook_returnsFalse() {
+ assertFalse(modelManager.hasGroup(TUT04));
+ }
+
+ @Test
+ public void hasGroup_groupInAddressBook_returnsTrue() {
+ modelManager.addGroup(TUT04);
+ assertTrue(modelManager.hasGroup(TUT04));
+ }
+
@Test
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
diff --git a/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java
new file mode 100644
index 00000000000..7d22ca788ba
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java
@@ -0,0 +1,83 @@
+package seedu.address.model.group;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+public class GroupContainsKeywordsPredicateTest {
+
+ @Test
+ public void equals() {
+ List firstPredicateKeywordList = Collections.singletonList("TUT01");
+ List secondPredicateKeywordList = Arrays.asList("TUT01", "TUT02");
+
+ GroupContainsKeywordsPredicate firstPredicate = new GroupContainsKeywordsPredicate(firstPredicateKeywordList);
+ GroupContainsKeywordsPredicate secondPredicate = new GroupContainsKeywordsPredicate(secondPredicateKeywordList);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ GroupContainsKeywordsPredicate firstPredicateCopy =
+ new GroupContainsKeywordsPredicate(firstPredicateKeywordList);
+
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ // different types -> returns false
+ assertFalse(firstPredicate.equals(1));
+
+ // null -> returns false
+ assertFalse(firstPredicate.equals(null));
+
+ // different person -> returns false
+ assertFalse(firstPredicate.equals(secondPredicate));
+ }
+
+ @Test
+ public void test_nameContainsKeywords_returnsTrue() {
+ // One keyword
+ GroupContainsKeywordsPredicate predicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("TUT10"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").withGroups("TUT10").build()));
+
+ // Multiple keywords
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT10", "LAB05"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").withGroups("TUT10").build()));
+
+ // Only one matching keyword
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT10", "LAB05"));
+ assertTrue(predicate.test(
+ new PersonBuilder().withName("Alice Carol").withGroups("TUT10", "LAB05").build()));
+ }
+
+ @Test
+ public void test_nameDoesNotContainKeywords_returnsFalse() {
+ // Zero keywords
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(Collections.emptyList());
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").build()));
+
+ // Non-matching keyword
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("TUT10"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").withGroups("TUT11").build()));
+
+ }
+
+ @Test
+ public void toStringMethod() {
+ List keywords = List.of("TUT01", "TUT02");
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(keywords);
+
+ String expected = GroupContainsKeywordsPredicate.class.getCanonicalName() + "{groups="
+ + keywords.stream().map(Group::new).collect(Collectors.toList()) + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/group/GroupTest.java b/src/test/java/seedu/address/model/group/GroupTest.java
new file mode 100644
index 00000000000..5c2a9274456
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupTest.java
@@ -0,0 +1,60 @@
+package seedu.address.model.group;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class GroupTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Group(null));
+ }
+
+ @Test
+ public void constructor_invalidGroupName_throwsIllegalArgumentException() {
+ String invalidGroupName = "";
+ assertThrows(IllegalArgumentException.class, () -> new Group(invalidGroupName));
+ }
+
+ @Test
+ public void isValidGroupName() {
+ // null group name
+ assertThrows(NullPointerException.class, () -> Group.isValidGroupName(null));
+ assertTrue(Group.isValidGroupName("TUT01"));
+ assertTrue(Group.isValidGroupName("LAB01"));
+ assertTrue(Group.isValidGroupName("REC01"));
+ assertFalse(Group.isValidGroupName("a"));
+ }
+
+ @Test
+ public void isValidLink() {
+ // null group name
+ assertThrows(NullPointerException.class, () -> Group.isValidLink(null));
+ assertTrue(Group.isValidLink("https://t.me/asdfasdf"));
+ assertFalse(Group.isValidLink("a"));
+ }
+
+ @Test
+ public void isSameGroup() {
+ Group group = new Group("TUT01");
+
+ assertTrue(group.isSameGroup(group)); // same group
+ assertTrue(group.isSameGroup(new Group("TUT01"))); // same group name
+ assertTrue(group.isSameGroup(new Group("TUT01", "https://t.me/abcdefghij")));
+ assertFalse(group.isSameGroup(new Group("LAB01")));
+ }
+
+ @Test
+ public void equals() {
+ Group group = new Group("TUT01");
+
+ assertTrue(group.equals(group)); // same object
+ assertTrue(group.equals(new Group("TUT01"))); // same group name
+ assertTrue(group.equals(new Group("TUT01", "https://t.me/abcdefghij")));
+ assertFalse(group.equals(new Group("LAB01")));
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/group/UniqueGroupListTest.java b/src/test/java/seedu/address/model/group/UniqueGroupListTest.java
new file mode 100644
index 00000000000..be324bfead4
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/UniqueGroupListTest.java
@@ -0,0 +1,225 @@
+package seedu.address.model.group;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.LAB10;
+import static seedu.address.testutil.TypicalGroups.TUT04;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.group.exceptions.DuplicateGroupException;
+import seedu.address.model.group.exceptions.GroupNotFoundException;
+
+public class UniqueGroupListTest {
+
+ private final UniqueGroupList uniqueGroupList = new UniqueGroupList();
+
+ @Test
+ public void contains_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.contains(null));
+ }
+
+ @Test
+ public void contains_groupNotInList_returnsFalse() {
+ assertFalse(uniqueGroupList.contains(TUT04));
+ }
+
+ @Test
+ public void contains_groupInList_returnsTrue() {
+ uniqueGroupList.add(TUT04);
+ assertTrue(uniqueGroupList.contains(TUT04));
+ }
+
+ @Test
+ public void add_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.add(null));
+ }
+
+ @Test
+ public void add_duplicateGroup_throwsDuplicateGroupException() {
+ uniqueGroupList.add(TUT04);
+ assertThrows(DuplicateGroupException.class, () -> uniqueGroupList.add(TUT04));
+ }
+
+ @Test
+ public void setGroup_nullTargetGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.setGroup(null, TUT04));
+ }
+
+ @Test
+ public void setGroup_nullEditedGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.setGroup(TUT04, null));
+ }
+
+ @Test
+ public void setGroup_targetGroupNotInList_throwsGroupNotFoundException() {
+ assertThrows(GroupNotFoundException.class, () -> uniqueGroupList.setGroup(TUT04, TUT04));
+ }
+
+ @Test
+ public void setGroup_editedGroupIsSameGroup_success() {
+ uniqueGroupList.add(TUT04);
+ uniqueGroupList.setGroup(TUT04, TUT04);
+ UniqueGroupList expectedUniqueGroupList = new UniqueGroupList();
+ expectedUniqueGroupList.add(TUT04);
+ assertEquals(expectedUniqueGroupList, uniqueGroupList);
+ }
+
+ @Test
+ public void setGroup_editedGroupHasDifferentIdentity_success() {
+ uniqueGroupList.add(TUT04);
+ uniqueGroupList.setGroup(TUT04, LAB10);
+ UniqueGroupList expectedUniqueGroupList = new UniqueGroupList();
+ expectedUniqueGroupList.add(LAB10);
+ assertEquals(expectedUniqueGroupList, uniqueGroupList);
+ }
+
+ @Test
+ public void setGroup_editedGroupHasNonUniqueIdentity_throwsDuplicateGroupException() {
+ uniqueGroupList.add(TUT04);
+ uniqueGroupList.add(LAB10);
+ assertThrows(DuplicateGroupException.class, () -> uniqueGroupList.setGroup(TUT04, LAB10));
+ }
+
+ @Test
+ public void remove_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.remove(null));
+ }
+
+ @Test
+ public void remove_groupDoesNotExist_throwsGroupNotFoundException() {
+ assertThrows(GroupNotFoundException.class, () -> uniqueGroupList.remove(TUT04));
+ }
+
+ @Test
+ public void remove_existingGroup_removesGroup() {
+ uniqueGroupList.add(TUT04);
+ uniqueGroupList.remove(TUT04);
+ UniqueGroupList expectedUniqueGroupList = new UniqueGroupList();
+ assertEquals(expectedUniqueGroupList, uniqueGroupList);
+ }
+
+ @Test
+ public void setGroups_nullUniqueGroupList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.setGroups((UniqueGroupList) null));
+ }
+
+ @Test
+ public void setGroups_uniqueGroupList_replacesOwnListWithProvidedUniqueGroupList() {
+ uniqueGroupList.add(TUT04);
+ UniqueGroupList expectedUniqueGroupList = new UniqueGroupList();
+ expectedUniqueGroupList.add(LAB10);
+ uniqueGroupList.setGroups(expectedUniqueGroupList);
+ assertEquals(expectedUniqueGroupList, uniqueGroupList);
+ }
+
+ @Test
+ public void setGroups_nullList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueGroupList.setGroups((List) null));
+ }
+
+ @Test
+ public void setGroups_list_replacesOwnListWithProvidedList() {
+ uniqueGroupList.add(TUT04);
+ List groupList = Collections.singletonList(LAB10);
+ uniqueGroupList.setGroups(groupList);
+ UniqueGroupList expectedUniqueGroupList = new UniqueGroupList();
+ expectedUniqueGroupList.add(LAB10);
+ assertEquals(expectedUniqueGroupList, uniqueGroupList);
+ }
+
+ @Test
+ public void setGroups_listWithDuplicateGroups_throwsDuplicateGroupException() {
+ List listWithDuplicateGroups = Arrays.asList(TUT04, TUT04);
+ assertThrows(DuplicateGroupException.class, () -> uniqueGroupList.setGroups(listWithDuplicateGroups));
+ }
+
+ @Test
+ public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, ()
+ -> uniqueGroupList.asUnmodifiableObservableList().remove(0));
+ }
+
+ @Test
+ public void equals() {
+ UniqueGroupList uniqueGroupListCopy = new UniqueGroupList();
+ uniqueGroupListCopy.add(TUT04);
+ uniqueGroupList.add(TUT04);
+ assertTrue(uniqueGroupList.equals(uniqueGroupListCopy));
+ }
+
+ @Test
+ public void toStringMethod() {
+ assertEquals(uniqueGroupList.asUnmodifiableObservableList().toString(), uniqueGroupList.toString());
+ }
+
+ @Test
+ public void hashCode_equalObjectsSameHashCode() {
+ Group group1 = new Group("TUT01");
+ Group group2 = new Group("TUT01");
+
+ UniqueGroupList list1 = new UniqueGroupList();
+ list1.add(group1);
+
+ UniqueGroupList list2 = new UniqueGroupList();
+ list2.add(group2);
+
+ assertEquals(list1.hashCode(), list2.hashCode());
+ }
+
+ @Test
+ public void hashCode_diffObjectsDiffHashCode() {
+ Group group1 = new Group("TUT01");
+ Group group2 = new Group("TUT02");
+
+ UniqueGroupList list1 = new UniqueGroupList();
+ list1.add(group1);
+
+ UniqueGroupList list2 = new UniqueGroupList();
+ list2.add(group2);
+
+ assertNotEquals(list1.hashCode(), list2.hashCode());
+ }
+
+ @Test
+ public void iterator_iterationOverElements_returnsAllElements() {
+ // Create a list of groups
+ List groupList = new ArrayList<>();
+ groupList.add(new Group("TUT01"));
+ groupList.add(new Group("TUT02"));
+ groupList.add(new Group("TUT03"));
+
+ // Create a UniqueGroupList
+ UniqueGroupList uniqueGroupList = new UniqueGroupList();
+ uniqueGroupList.setGroups(groupList);
+
+ // Iterate over the elements using iterator
+ Iterator iterator = uniqueGroupList.iterator();
+ int count = 0;
+ while (iterator.hasNext()) {
+ Group nextGroup = iterator.next();
+ // Ensure that the next element returned by the iterator is from the original list
+ assertEquals(groupList.get(count), nextGroup);
+ count++;
+ }
+
+ // Ensure that the iterator traverses all elements
+ assertEquals(groupList.size(), count);
+ }
+
+ @Test
+ public void iterator_iterationOverEmptyList_returnsEmptyIterator() {
+ UniqueGroupList uniqueGroupList = new UniqueGroupList();
+ Iterator iterator = uniqueGroupList.iterator();
+ assertFalse(iterator.hasNext());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java
deleted file mode 100644
index 314885eca26..00000000000
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package seedu.address.model.person;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.testutil.Assert.assertThrows;
-
-import org.junit.jupiter.api.Test;
-
-public class AddressTest {
-
- @Test
- public void constructor_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new Address(null));
- }
-
- @Test
- public void constructor_invalidAddress_throwsIllegalArgumentException() {
- String invalidAddress = "";
- assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress));
- }
-
- @Test
- public void isValidAddress() {
- // null address
- assertThrows(NullPointerException.class, () -> Address.isValidAddress(null));
-
- // invalid addresses
- assertFalse(Address.isValidAddress("")); // empty string
- assertFalse(Address.isValidAddress(" ")); // spaces only
-
- // valid addresses
- assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355"));
- assertTrue(Address.isValidAddress("-")); // one character
- assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address
- }
-
- @Test
- public void equals() {
- Address address = new Address("Valid Address");
-
- // same values -> returns true
- assertTrue(address.equals(new Address("Valid Address")));
-
- // same object -> returns true
- assertTrue(address.equals(address));
-
- // null -> returns false
- assertFalse(address.equals(null));
-
- // different types -> returns false
- assertFalse(address.equals(5.0f));
-
- // different values -> returns false
- assertFalse(address.equals(new Address("Other Valid Address")));
- }
-}
diff --git a/src/test/java/seedu/address/model/person/MajorTest.java b/src/test/java/seedu/address/model/person/MajorTest.java
new file mode 100644
index 00000000000..f59492e29db
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/MajorTest.java
@@ -0,0 +1,59 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class MajorTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Major(null));
+ }
+
+ @Test
+ public void constructor_invalidMajor_throwsIllegalArgumentException() {
+ String invalidMajor = "";
+ assertThrows(IllegalArgumentException.class, () -> new Major(invalidMajor));
+ }
+
+ @Test
+ public void isValidMajor() {
+ // null major
+ assertThrows(NullPointerException.class, () -> Major.isValidMajor(null));
+
+ // invalid majors
+ assertFalse(Major.isValidMajor("")); // empty string
+ assertFalse(Major.isValidMajor(" ")); // spaces only
+
+ // valid majors (Computing majors)
+ assertTrue(Major.isValidMajor("-")); // one character
+ assertTrue(Major.isValidMajor("Business Analytics"));
+ assertTrue(Major.isValidMajor("Computer Science"));
+ assertTrue(Major.isValidMajor("Computer Engineering"));
+ assertTrue(Major.isValidMajor("Information Systems"));
+ assertTrue(Major.isValidMajor("Information Security"));
+ }
+
+ @Test
+ public void equals() {
+ Major major = new Major("Valid Major");
+
+ // same values -> returns true
+ assertTrue(major.equals(new Major("Valid Major")));
+
+ // same object -> returns true
+ assertTrue(major.equals(major));
+
+ // null -> returns false
+ assertFalse(major.equals(null));
+
+ // different types -> returns false
+ assertFalse(major.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(major.equals(new Major("Other Valid Major")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
index 6b3fd90ade7..6dfe0d493c4 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
@@ -69,9 +69,9 @@ public void test_nameDoesNotContainKeywords_returnsFalse() {
assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
// Keywords match phone, email and address, but does not match name
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street"));
+ predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Computer", "Science"));
assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345")
- .withEmail("alice@email.com").withAddress("Main Street").build()));
+ .withEmail("alice@email.com").withMajor("Computer Science").build()));
}
@Test
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java
index 94e3dd726bd..cf6bbd175a8 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/person/NameTest.java
@@ -36,6 +36,8 @@ public void isValidName() {
assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters
assertTrue(Name.isValidName("Capital Tan")); // with capital letters
assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names
+ assertTrue(Name.isValidName("Martin Luther King Jr.")); // containing period
+ assertTrue(Name.isValidName("Raju s/o Siva")); // containing slashes
}
@Test
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..5d8a106a827 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -3,11 +3,12 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_REMARK_OUTSPOKEN;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
@@ -21,7 +22,7 @@ public class PersonTest {
@Test
public void asObservableList_modifyList_throwsUnsupportedOperationException() {
Person person = new PersonBuilder().build();
- assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0));
+ assertThrows(UnsupportedOperationException.class, () -> person.getGroups().remove(0));
}
@Test
@@ -34,7 +35,7 @@ public void isSamePerson() {
// same name, all other attributes different -> returns true
Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
- .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
+ .withMajor(VALID_MAJOR_BOB).withRemark(VALID_REMARK_OUTSPOKEN).withGroups(VALID_GROUP_LAB).build();
assertTrue(ALICE.isSamePerson(editedAlice));
// different name, all other attributes same -> returns false
@@ -81,19 +82,52 @@ public void equals() {
editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build();
assertFalse(ALICE.equals(editedAlice));
- // different address -> returns false
- editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
+ // different major -> returns false
+ editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).build();
assertFalse(ALICE.equals(editedAlice));
- // different tags -> returns false
- editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
+ // different remark -> returns false
+ editedAlice = new PersonBuilder(ALICE).withRemark(VALID_REMARK_OUTSPOKEN).build();
assertFalse(ALICE.equals(editedAlice));
}
+ @Test
+ public void hashCodeMethod() {
+ // same values -> returns same hashcode
+ Person aliceCopy = new PersonBuilder(ALICE).build();
+ assertEquals(ALICE.hashCode(), aliceCopy.hashCode());
+
+ // different name -> returns different hashcode
+ Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+
+ // different phone -> returns different hashcode
+ editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+
+ // different email -> returns different hashcode
+ editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+
+ // different major -> returns different hashcode
+ editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+
+ // different remark -> returns different hashcode
+ editedAlice = new PersonBuilder(ALICE).withRemark(VALID_REMARK_OUTSPOKEN).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+
+ // different groups -> returns different hashcode
+ editedAlice = new PersonBuilder(ALICE).withGroups(VALID_GROUP_LAB).build();
+ assertFalse(ALICE.hashCode() == editedAlice.hashCode());
+ }
+
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", year=" + ALICE.getYear() + ", telegram=" + ALICE.getTelegram()
+ + ", major=" + ALICE.getMajor() + ", remark=" + ALICE.getRemark()
+ + ", groups=" + ALICE.getGroups() + "}";
assertEquals(expected, ALICE.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/RemarkTest.java b/src/test/java/seedu/address/model/person/RemarkTest.java
new file mode 100644
index 00000000000..fafaa6336ec
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/RemarkTest.java
@@ -0,0 +1,35 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class RemarkTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Remark(null));
+ }
+
+ @Test
+ public void equals() {
+ Remark remark = new Remark("#shy123");
+
+ // same values -> returns true
+ assertTrue(remark.equals(new Remark("#shy123")));
+
+ // same object -> returns true
+ assertTrue(remark.equals(remark));
+
+ // null -> returns false
+ assertFalse(remark.equals(null));
+
+ // different types -> returns false
+ assertFalse(remark.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(remark.equals(new Remark("different value")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/TelegramTest.java b/src/test/java/seedu/address/model/person/TelegramTest.java
new file mode 100644
index 00000000000..d0ccda8dd3d
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/TelegramTest.java
@@ -0,0 +1,57 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class TelegramTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Telegram(null));
+ }
+
+ @Test
+ public void constructor_invalidTelegram_throwsIllegalArgumentException() {
+ String invalidTelegram = "";
+ assertThrows(IllegalArgumentException.class, () -> new Telegram(invalidTelegram));
+ }
+
+ @Test
+ public void isValidTelegram() {
+ // null telegram
+ assertThrows(NullPointerException.class, () -> Telegram.isValidTelegram(null));
+
+ // invalid telegrams
+ assertFalse(Telegram.isValidTelegram("")); // empty string
+ assertFalse(Telegram.isValidTelegram(" ")); // spaces only
+ assertFalse(Telegram.isValidTelegram("a")); // less than 3 characters
+ assertFalse(Telegram.isValidTelegram("asdf!")); // alphabets with invalid characters
+
+ // valid telegrams
+ assertTrue(Telegram.isValidTelegram("asdf123")); // alphanumeric
+ assertTrue(Telegram.isValidTelegram("asdf123_")); // alphanumeric with underscore
+ }
+
+ @Test
+ public void equals() {
+ Telegram telegram = new Telegram("telegram123");
+
+ // same values -> returns true
+ assertTrue(telegram.equals(new Telegram("telegram123")));
+
+ // same object -> returns true
+ assertTrue(telegram.equals(telegram));
+
+ // null -> returns false
+ assertFalse(telegram.equals(null));
+
+ // different types -> returns false
+ assertFalse(telegram.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(telegram.equals(new Telegram("123telegram")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..480f203607d 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -3,8 +3,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
@@ -42,7 +42,7 @@ public void contains_personInList_returnsTrue() {
@Test
public void contains_personWithSameIdentityFieldsInList_returnsTrue() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).withGroups(VALID_GROUP_LAB)
.build();
assertTrue(uniquePersonList.contains(editedAlice));
}
@@ -85,7 +85,7 @@ public void setPerson_editedPersonIsSamePerson_success() {
@Test
public void setPerson_editedPersonHasSameIdentity_success() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withMajor(VALID_MAJOR_BOB).withGroups(VALID_GROUP_LAB)
.build();
uniquePersonList.setPerson(ALICE, editedAlice);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
diff --git a/src/test/java/seedu/address/model/person/YearTest.java b/src/test/java/seedu/address/model/person/YearTest.java
new file mode 100644
index 00000000000..4d3395a53c4
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/YearTest.java
@@ -0,0 +1,61 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class YearTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Year(null));
+ }
+
+ @Test
+ public void constructor_invalidPhone_throwsIllegalArgumentException() {
+ String invalidPhone = "";
+ assertThrows(IllegalArgumentException.class, () -> new Year(invalidPhone));
+ }
+
+ @Test
+ public void isValidYear() {
+ // null phone number
+ assertThrows(NullPointerException.class, () -> Year.isValidYear(null));
+
+ // invalid years
+ assertFalse(Year.isValidYear("")); // empty string
+ assertFalse(Year.isValidYear(" ")); // spaces only
+ assertFalse(Year.isValidYear("7")); // less than 3 numbers
+ assertFalse(Year.isValidYear("year")); // non-numeric
+ assertFalse(Year.isValidYear("a1")); // alphabets with digit
+
+ // valid years
+ assertTrue(Year.isValidYear("1")); // exactly year 1
+ assertTrue(Year.isValidYear("2")); // exactly year 2
+ assertTrue(Year.isValidYear("3")); // exactly year 3
+ assertTrue(Year.isValidYear("4")); // exactly year 4
+ assertTrue(Year.isValidYear("5")); // exactly year 5
+ }
+
+ @Test
+ public void equals() {
+ Year year = new Year("1");
+
+ // same values -> returns true
+ assertTrue(year.equals(new Year("1")));
+
+ // same object -> returns true
+ assertTrue(year.equals(year));
+
+ // null -> returns false
+ assertFalse(year.equals(null));
+
+ // different types -> returns false
+ assertFalse(year.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(year.equals(new Year("2")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java
deleted file mode 100644
index 64d07d79ee2..00000000000
--- a/src/test/java/seedu/address/model/tag/TagTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package seedu.address.model.tag;
-
-import static seedu.address.testutil.Assert.assertThrows;
-
-import org.junit.jupiter.api.Test;
-
-public class TagTest {
-
- @Test
- public void constructor_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new Tag(null));
- }
-
- @Test
- public void constructor_invalidTagName_throwsIllegalArgumentException() {
- String invalidTagName = "";
- assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName));
- }
-
- @Test
- public void isValidTagName() {
- // null tag name
- assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null));
- }
-
-}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..15869651fe2 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -5,31 +5,37 @@
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.BENSON;
-import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
public class JsonAdaptedPersonTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
- private static final String INVALID_ADDRESS = " ";
+ private static final String INVALID_MAJOR = " ";
private static final String INVALID_EMAIL = "example.com";
- private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_YEAR = "7";
+ private static final String INVALID_TELEGRAM = "inval!d";
+ private static final String INVALID_GROUP = "TUT9301391";
private static final String VALID_NAME = BENSON.getName().toString();
private static final String VALID_PHONE = BENSON.getPhone().toString();
private static final String VALID_EMAIL = BENSON.getEmail().toString();
- private static final String VALID_ADDRESS = BENSON.getAddress().toString();
- private static final List VALID_TAGS = BENSON.getTags().stream()
- .map(JsonAdaptedTag::new)
+ private static final String VALID_YEAR = BENSON.getYear().toString();
+ private static final String VALID_MAJOR = BENSON.getMajor().toString();
+ private static final String VALID_TELEGRAM = BENSON.getTelegram().toString();
+ private static final String VALID_REMARK = BENSON.getRemark().toString();
+ private static final List VALID_GROUPS = BENSON.getGroups().stream()
+ .map(JsonAdaptedPersonGroupAttendance::new)
.collect(Collectors.toList());
@Test
@@ -40,30 +46,35 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
- JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,40 +82,70 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_invalidAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
- String expectedMessage = Address.MESSAGE_CONSTRAINTS;
+ public void toModelType_invalidTelegram_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ INVALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+ String expectedMessage = Telegram.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
+ public void toModelType_nullTelegram_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ null, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Telegram.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_invalidTags_throwsIllegalValueException() {
- List invalidTags = new ArrayList<>(VALID_TAGS);
- invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
+ public void toModelType_invalidMajor_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
- assertThrows(IllegalValueException.class, person::toModelType);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_YEAR, VALID_TELEGRAM,
+ INVALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+ String expectedMessage = Major.MESSAGE_CONSTRAINTS;
+
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidYear_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_YEAR,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+
+ String expectedMessage = Year.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullYear_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null,
+ VALID_TELEGRAM, VALID_MAJOR, VALID_REMARK, VALID_GROUPS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Year.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+ @Test
+ public void toModelType_nullMajor_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_YEAR,
+ VALID_TELEGRAM, null, VALID_REMARK, VALID_GROUPS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Major.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
}
diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
index 4e5ce9200c8..9d107a34df1 100644
--- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
+++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
@@ -3,10 +3,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.HOON;
import static seedu.address.testutil.TypicalPersons.IDA;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import java.io.IOException;
import java.nio.file.Path;
diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
index 188c9058d20..23a999ca03c 100644
--- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
+++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
@@ -11,6 +11,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.commons.util.JsonUtil;
import seedu.address.model.AddressBook;
+import seedu.address.testutil.TypicalGroups;
import seedu.address.testutil.TypicalPersons;
public class JsonSerializableAddressBookTest {
@@ -19,6 +20,7 @@ public class JsonSerializableAddressBookTest {
private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json");
private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json");
private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json");
+ private static final Path TYPICAL_GROUPS_FILE = TEST_DATA_FOLDER.resolve("typicalGroupsAddressBook.json");
@Test
public void toModelType_typicalPersonsFile_success() throws Exception {
@@ -44,4 +46,13 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex
dataFromFile::toModelType);
}
+ @Test
+ public void toModelType_typicalGroupsFile_success() throws Exception {
+ JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_GROUPS_FILE,
+ JsonSerializableAddressBook.class).get();
+
+ AddressBook addressBookFromFile = dataFromFile.toModelType();
+ AddressBook typicalGroupsAddressBook = TypicalGroups.getTypicalAddressBook();
+ assertEquals(addressBookFromFile, typicalGroupsAddressBook);
+ }
}
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..5457e96ed5a 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -2,7 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalAddressBook.getTypicalAddressBook;
import java.nio.file.Path;
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..0742e3a7030 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -5,12 +5,15 @@
import java.util.stream.Stream;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
/**
* A utility class to help with building EditPersonDescriptor objects.
@@ -35,8 +38,10 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setName(person.getName());
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
- descriptor.setAddress(person.getAddress());
- descriptor.setTags(person.getTags());
+ descriptor.setMajor(person.getMajor());
+ descriptor.setTelegram(person.getTelegram());
+ descriptor.setRemark(person.getRemark());
+ descriptor.setGroups(person.getGroups());
}
/**
@@ -64,20 +69,44 @@ public EditPersonDescriptorBuilder withEmail(String email) {
}
/**
- * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building.
+ * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withYear(String year) {
+ descriptor.setYear(new Year(year));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withTelegram(String year) {
+ descriptor.setTelegram(new Telegram(year));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Major} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withMajor(String major) {
+ descriptor.setMajor(new Major(major));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Remark} of the {@code EditPersonDescriptor} that we are building.
*/
- public EditPersonDescriptorBuilder withAddress(String address) {
- descriptor.setAddress(new Address(address));
+ public EditPersonDescriptorBuilder withRemark(String remark) {
+ descriptor.setRemark(new Remark(remark));
return this;
}
/**
- * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
+ * Parses the {@code groups} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
*/
- public EditPersonDescriptorBuilder withTags(String... tags) {
- Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
- descriptor.setTags(tagSet);
+ public EditPersonDescriptorBuilder withGroups(String... groups) {
+ Set groupSet = Stream.of(groups).map(Group::new).collect(Collectors.toSet());
+ descriptor.setGroups(groupSet);
return this;
}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..226c7b236cf 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -3,12 +3,15 @@
import java.util.HashSet;
import java.util.Set;
-import seedu.address.model.person.Address;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Major;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Telegram;
+import seedu.address.model.person.Year;
import seedu.address.model.util.SampleDataUtil;
/**
@@ -19,13 +22,18 @@ public class PersonBuilder {
public static final String DEFAULT_NAME = "Amy Bee";
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
- public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
-
+ public static final String DEFAULT_YEAR = "1";
+ public static final String DEFAULT_MAJOR = "Computer Science";
+ public static final String DEFAULT_TELEGRAM = "amy123";
+ public static final String DEFAULT_REMARK = "shy";
private Name name;
private Phone phone;
private Email email;
- private Address address;
- private Set tags;
+ private Year year;
+ private Major major;
+ private Telegram telegram;
+ private Remark remark;
+ private Set groups;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -34,8 +42,11 @@ public PersonBuilder() {
name = new Name(DEFAULT_NAME);
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
- address = new Address(DEFAULT_ADDRESS);
- tags = new HashSet<>();
+ year = new Year(DEFAULT_YEAR);
+ major = new Major(DEFAULT_MAJOR);
+ telegram = new Telegram(DEFAULT_TELEGRAM);
+ remark = new Remark(DEFAULT_REMARK);
+ groups = new HashSet<>();
}
/**
@@ -45,8 +56,11 @@ public PersonBuilder(Person personToCopy) {
name = personToCopy.getName();
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
- address = personToCopy.getAddress();
- tags = new HashSet<>(personToCopy.getTags());
+ year = personToCopy.getYear();
+ major = personToCopy.getMajor();
+ telegram = personToCopy.getTelegram();
+ remark = personToCopy.getRemark();
+ groups = new HashSet<>(personToCopy.getGroups());
}
/**
@@ -58,18 +72,18 @@ public PersonBuilder withName(String name) {
}
/**
- * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
+ * Parses the {@code groups} into a {@code Set} and set it to the {@code Person} that we are building.
*/
- public PersonBuilder withTags(String ... tags) {
- this.tags = SampleDataUtil.getTagSet(tags);
+ public PersonBuilder withGroups(String ... groups) {
+ this.groups = SampleDataUtil.getGroupSet(groups);
return this;
}
/**
- * Sets the {@code Address} of the {@code Person} that we are building.
+ * Sets the {@code Major} of the {@code Person} that we are building.
*/
- public PersonBuilder withAddress(String address) {
- this.address = new Address(address);
+ public PersonBuilder withMajor(String major) {
+ this.major = new Major(major);
return this;
}
@@ -89,8 +103,32 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code Telegram} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withTelegram(String telegram) {
+ this.telegram = new Telegram(telegram);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Year} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withYear(String year) {
+ this.year = new Year(year);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Remark} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withRemark(String remark) {
+ this.remark = new Remark(remark);
+ return this;
+ }
+
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, year, telegram, major, remark, groups);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..486e4447131 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,17 +1,20 @@
package seedu.address.testutil;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR;
import java.util.Set;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
-import seedu.address.model.tag.Tag;
/**
* A utility class for Person.
@@ -33,9 +36,12 @@ public static String getPersonDetails(Person person) {
sb.append(PREFIX_NAME + person.getName().fullName + " ");
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
- sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
- person.getTags().stream().forEach(
- s -> sb.append(PREFIX_TAG + s.tagName + " ")
+ sb.append(PREFIX_YEAR + person.getYear().value + " ");
+ sb.append(PREFIX_MAJOR + person.getMajor().value + " ");
+ sb.append(PREFIX_TELEGRAM + person.getTelegram().value + " ");
+ sb.append(PREFIX_REMARK + person.getRemark().value + " ");
+ person.getGroups().stream().forEach(
+ s -> sb.append(PREFIX_GROUP + s.groupName + " ")
);
return sb.toString();
}
@@ -48,13 +54,16 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" "));
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
- descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
- if (descriptor.getTags().isPresent()) {
- Set tags = descriptor.getTags().get();
- if (tags.isEmpty()) {
- sb.append(PREFIX_TAG);
+ descriptor.getMajor().ifPresent(major -> sb.append(PREFIX_MAJOR).append(major.value).append(" "));
+ descriptor.getYear().ifPresent(year -> sb.append(PREFIX_YEAR).append(year.value).append(" "));
+ descriptor.getTelegram().ifPresent(telegram -> sb.append(PREFIX_TELEGRAM).append(telegram.value).append(" "));
+ descriptor.getRemark().ifPresent(remark-> sb.append(PREFIX_REMARK).append(remark.value).append(" "));
+ if (descriptor.getGroups().isPresent()) {
+ Set groups = descriptor.getGroups().get();
+ if (groups.isEmpty()) {
+ sb.append(PREFIX_GROUP);
} else {
- tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" "));
+ groups.forEach(s -> sb.append(PREFIX_GROUP).append(s.groupName).append(" "));
}
}
return sb.toString();
diff --git a/src/test/java/seedu/address/testutil/TypicalAddressBook.java b/src/test/java/seedu/address/testutil/TypicalAddressBook.java
new file mode 100644
index 00000000000..fa4f1a6cf0e
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalAddressBook.java
@@ -0,0 +1,26 @@
+package seedu.address.testutil;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * A utility class containing a list of {@code Person} objects to be used in tests.
+ */
+public class TypicalAddressBook {
+ /**
+ * Returns an {@code AddressBook} with all the typical persons.
+ */
+ public static AddressBook getTypicalAddressBook() {
+ AddressBook ab = new AddressBook();
+ for (Person person : TypicalPersons.getTypicalPersons()) {
+ ab.addPerson(person);
+ }
+
+ for (Group group : TypicalGroups.getTypicalGroups()) {
+ ab.addGroup(group);
+ }
+ return ab;
+ }
+
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalGroups.java b/src/test/java/seedu/address/testutil/TypicalGroups.java
new file mode 100644
index 00000000000..b8f787e3ec8
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalGroups.java
@@ -0,0 +1,37 @@
+package seedu.address.testutil;
+
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.group.Group;
+
+/**
+ * A utility class containing a list of {@code Group} objects to be used in tests.
+ */
+public class TypicalGroups {
+
+ public static final Group TUT04 = new Group(VALID_GROUP_TUTORIAL);
+ public static final Group LAB10 = new Group(VALID_GROUP_LAB);
+
+ private TypicalGroups() {} // prevents instantiation
+
+ /**
+ * Returns an {@code AddressBook} with all the typical persons.
+ */
+ public static AddressBook getTypicalAddressBook() {
+ AddressBook ab = new AddressBook();
+ for (Group group : getTypicalGroups()) {
+ ab.addGroup(group);
+ }
+ return ab;
+ }
+
+ public static List getTypicalGroups() {
+ return new ArrayList<>(Arrays.asList(TUT04, LAB10));
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..4b0f70b6374 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -1,15 +1,21 @@
package seedu.address.testutil;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_LAB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_TUTORIAL;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MAJOR_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_REMARK_OUTSPOKEN;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_REMARK_SHY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_YEAR_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_YEAR_BOB;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,35 +30,54 @@
public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
- .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
- .withPhone("94351253")
- .withTags("friends").build();
+ .withMajor("Computer Science").withEmail("alice@example.com")
+ .withYear("1").withTelegram("alicepauline")
+ .withPhone("94351253").withRemark("shy")
+ .withGroups("TUT04", "LAB10").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
- .withAddress("311, Clementi Ave 2, #02-25")
- .withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
- public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
- public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
- public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
- .withEmail("werner@example.com").withAddress("michegan ave").build();
- public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
- .withEmail("lydia@example.com").withAddress("little tokyo").build();
- public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
- .withEmail("anna@example.com").withAddress("4th street").build();
+ .withMajor("Computer Science")
+ .withYear("1").withTelegram("bensonmeier")
+ .withEmail("johnd@example.com").withPhone("98765432").withRemark("always skip tutorials")
+ .withGroups("TUT04", "LAB10").build();
+ public static final Person CARL = new PersonBuilder().withName("Carl Kurz")
+ .withPhone("95352563").withTelegram("carlkurz")
+ .withMajor("Computer Science").withEmail("heinz@example.com").withRemark("")
+ .withYear("1").withGroups("TUT04").build();
+ public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier")
+ .withPhone("87652533")
+ .withMajor("Computer Engineering").withEmail("corelia@example.com")
+ .withYear("1").withTelegram("danielmeier").withRemark("hardworking")
+ .withGroups("TUT04").build();
+ public static final Person ELLE = new PersonBuilder().withName("Elle Meyer")
+ .withPhone("9482224")
+ .withEmail("werner@example.com").withMajor("Computer Engineering")
+ .withYear("2").withTelegram("ellemeyer").withRemark("strong foundations")
+ .withGroups("TUT04").build();
+ public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz")
+ .withPhone("9482427")
+ .withEmail("lydia@example.com").withMajor("Business Analytics")
+ .withYear("2").withTelegram("fionakunz1").withRemark("Always ask questions in class")
+ .withGroups("TUT04").build();
+ public static final Person GEORGE = new PersonBuilder().withName("George Best")
+ .withPhone("9482442")
+ .withEmail("anna@example.com").withMajor("Business Analytics")
+ .withYear("2").withTelegram("georgebest1").withRemark("")
+ .withGroups("TUT04").build();
// Manually added
public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
- .withEmail("stefan@example.com").withAddress("little india").build();
+ .withEmail("stefan@example.com").withYear("2").withTelegram("hoon").withMajor("Computer Science").build();
public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
- .withEmail("hans@example.com").withAddress("chicago ave").build();
+ .withEmail("hans@example.com").withYear("3").withTelegram("ida1").withMajor("Business Analytics").build();
// Manually added - Person's details found in {@code CommandTestUtil}
public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
+ .withEmail(VALID_EMAIL_AMY).withYear(VALID_YEAR_AMY).withTelegram(VALID_TELEGRAM_AMY)
+ .withMajor(VALID_MAJOR_AMY).withRemark(VALID_REMARK_SHY).withGroups(VALID_GROUP_TUTORIAL).build();
public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withEmail(VALID_EMAIL_BOB).withYear(VALID_YEAR_BOB).withTelegram(VALID_TELEGRAM_BOB)
+ .withMajor(VALID_MAJOR_BOB).withRemark(VALID_REMARK_OUTSPOKEN).withGroups(VALID_GROUP_LAB,
+ VALID_GROUP_TUTORIAL)
.build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER