diff --git a/README.md b/README.md index 13f5c77403f..4b86357be6e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ -[![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/AY2122S2-CS2103T-W15-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103T-W15-3/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 -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +## Teaching Assistant Contact Helper (TACH) + +* This is a team project for a desktop application (called **TACH**) used by Computer Science (**CS**) +Teaching Assistants (**TA**s) in NUS to manage and keep track of their students in +the TAs tutorial groups. +* **TACH** aims to solve problems of CS TAs regarding management and alleviate their workload by: + * Managing their students in an organised manner + * Easily access a student's contact details like their email or Telegram + * Find students either by their name or by their tutorial group +* It is named `Teaching Assistant Contact Helper` (`TACH` for short) because it helps TAs with their students' contacts. +* For detailed documentation of this project, see the +**[TACH Product Website](https://ay2122s2-cs2103t-w15-3.github.io/tp/)** +* 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 be2d2905dde..a6587c12bf5 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven {url "https://sandec.jfrog.io/artifactory/repo"} } checkstyle { @@ -25,6 +26,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) @@ -44,6 +49,9 @@ dependencies { String jUnitVersion = '5.4.0' String javaFxVersion = '11' + compile 'org.apache.directory.studio:org.apache.commons.io:2.4' + compile "com.sandec:mdfx:0.2.4" + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..d9143e5719d 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,48 @@ title: 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` +You can reach us at the email `vanessakhor19@gmail.com` ## Project team -### John Doe +### Low Jia Hao - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/LowJiaHao99)] +[[portfolio](team/lowjiahao99.md)] -* Role: Project Advisor +* Role: Team lead +* Responsibilities: Model and API testing -### Jane Doe +### Lim Jan Jay - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] - -* Role: Team Lead -* Responsibilities: UI - -### Johnny Doe - - - -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/jaysmyname)] +[[portfolio](team/jaysmyname.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: UI and GUI testing -### Jean Doe +### Huang Qing - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/hqhqhq1)] +[[portfolio](team/hqhqhq1.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Storage, code quality, deliverables, deadlines, Scheduling and tracking + -### James Doe +### Khor Vanessa - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/vanessaxuuan)] +[[portfolio](team/vanessaxuuan.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Integration, logic and documentation + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..78b9142b0a3 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,14 +2,47 @@ layout: page title: Developer Guide --- + +## **Introduction** + +Teaching Assistant Contact Helper (TACH) is a desktop application for Computer Science (CS) Teaching Assistants (TAs) in National University of Singapore (NUS) to manage their students. The +application is highly optimised for users who can type fast as it is based on the Command Line Interface (CLI). Thus, +the main interaction with TACH will be done through user text-based commands. + +This developer’s guide assumes its readers to have a basic understanding of programming. + +The purpose of this Developer Guide is to help readers understand the design and implementation of TACH, so that +any reader who is interested can become a contributor to this project as well. + +-------------------------------------------------------------------------------------------------------------------- +## **Acknowledgements** + +- This project is based on the AddressBook-Level3(AB3) project created by the [SE-EDU initiative](https://se-education.org).
+- Libraries used: + - [JavaFX](https://openjfx.io/) + - [Jackson](https://github.com/FasterXML/jackson) + - [JUnit5](https://github.com/junit-team/junit5) + - [Markdown-javafx-renderer](https://github.com/JPro-one/markdown-javafx-renderer) + +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents + * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- -## **Acknowledgements** +## Navigation -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +Following are a few syntaxes to take note of before proceeding with the rest of the developer guide: + +| Syntax | Description | +|-------------------------------------------------------------------------|---------------------------------------------------------------| +| `Markdown` | Denotes file path, distinct classes, their usage or examples. | +|
:information_source: Note | Important information to take note of. | +| Words in `UPPER_CASE` | Parameters to be supplied by the user. | +| parameter end with `…` | This parameter can be added multiple times. | -------------------------------------------------------------------------------------------------------------------- @@ -23,12 +56,12 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/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. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2122S2-CS2103T-W15-3/tp/tree/master/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.
### Architecture - + The ***Architecture Diagram*** given above explains the high-level design of the App. @@ -36,7 +69,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`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). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -69,24 +102,24 @@ 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/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/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 consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `StudentListPanel`, `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/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * 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`. +* depends on some classes in the `Model` component, as it displays `Student` 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/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -95,7 +128,7 @@ Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). +1. The command can communicate with the `Model` when it is executed (e.g. to add a student). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -113,20 +146,22 @@ 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. +Go back to **[Table of Contents](#table-of-contents)** + ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/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 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 the address book data i.e., all `Student` objects (which are contained in a `UniqueStudentList` object). +* stores the currently 'selected' `Student` 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.
+
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `TutorialGroup` list in the `AddressBook`, which `Student` references. This allows `AddressBook` to only require one `TutorialGroup` object per unique tutorial group, instead of each `Student` needing their own `TutorialGroup` objects.
@@ -135,7 +170,7 @@ The `Model` component, ### 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/AY2122S2-CS2103T-W15-3/tp/tree/master/src/main/java/seedu/address/storage/Storage.java) @@ -148,96 +183,241 @@ The `Storage` component, Classes used by multiple components are in the `seedu.addressbook.commons` package. +Go back to **[Table of Contents](#table-of-contents)** + -------------------------------------------------------------------------------------------------------------------- ## **Implementation** +* [Telegram and GitHub attribute implementations](#telegram-and-github-attribute-implementations) + * [How it works](#how-it-works) + * [Why it works](#why-it-works) + * [Design considerations regarding how empty GitHub and Telegram should be stored](#design-considerations-regarding-how-empty-github-and-telegram-should-be-stored) +* [`addtg` feature](#addtg-feature) + * [How `addtg` command is parsed and executed](#how-addtg-command-is-parsed-and-executed) + * [Example usage scenario and how the `addtg` mechanism behaves](#example-usage-scenario-and-how-the-addtg-mechanism-behaves) +* [`deletetg` feature](#deletetg-feature) + * [How `deletetg` command is parsed and executed](#how-deletetg-command-is-parsed-and-executed) + * [A few interesting details regarding how `deletetg` command works](#a-few-interesting-details-regarding-how-deletetg-command-works) +* [`findtg` feature](#findtg-feature) + * [How `findtg` command is parsed and executed](#how-findtg-command-is-parsed-and-executed) + * [Example usage scenario and how the `findtg` mechanism behaves](#example-usage-scenario-and-how-the-findtg-mechanism-behaves) + This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Telegram and GitHub attribute implementations -#### Proposed Implementation +The diagram below shows that a `Student` may or may not have a `Telegram` and a `Github`. -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. +Students with empty +`GitHub` and `Telegram` are stored using `GitHub` and `Telegram` instantiated with empty strings as shown below. -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. +#### How it works -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. +Below is a sequence diagram for `addStudentCommand`. The command was implemented such that all inputs have to be parsed by the respective methods of `ParserUtil`. -![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. +>**Note:** parseGitHub and parseTelegram methods now accommodate null as inputs.
+> Here is a snippet for parseGitHub. parseTelegram has a similar format as well. +> ``` +> public static GitHub parseGitHub(String gitHub) throws ParseException { +> if (gitHub == null) { +> return new GitHub(null); +> } +> String trimmedGitHub = gitHub.trim(); +> if (!GitHub.isValidGitHub(trimmedGitHub)) { +> throw new ParseException(GitHub.MESSAGE_CONSTRAINTS); +> } +> return new GitHub(trimmedGitHub); +> } +> ``` -![UndoRedoState1](images/UndoRedoState1.png) +GitHub and Telegram objects instantiated with null inputs have a value of "" +Here is a snippet for the constructor of Telegram. GitHub also have a similar format. +``` +public Telegram(String telegram) { + if (telegram == null) { //if telegram is empty it will exist as an empty string + value = ""; + } else { + checkArgument(isValidTelegram(telegram), MESSAGE_CONSTRAINTS); + value = telegram; + } +} +``` -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`. +This means that an empty `GitHub` object will have a "" value and a `GitHub` object with a value of "" means that it is an empty `GitHub` object. The same logic applies to `Telegram` objects as well. -![UndoRedoState2](images/UndoRedoState2.png) +#### Why it works -
: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`. +As shown in the previous sequence diagram, `ParserUtil` parses all the inputs for the add command. Thus, an empty string (i.e. "") will be parsed though the method isValidXX, where XX is an attribute i.e. isValidName. All empty string will throw an error in any of parse methods in `ParserUtil`. Thus, an empty string will never be able to be accepted through the user input. Therefore, an empty string was used as a means to identify and instantiate attributes that can be empty (e.g. GitHub and Telegram). -
+#### Design considerations regarding how empty GitHub and Telegram should be stored -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. +* Alternative 1: Stored as null + * Pros: Easy to implement + * Cons: NullPointerException can occur if `.toString()`of null is called -![UndoRedoState3](images/UndoRedoState3.png) +* Alternative 2: Stored as a reserved valid string e.g. "null" + * Pros: Avoid NullPointerExceptions + * Cons: Possibility of a student whose telegram or github be the string "null". -
: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 -than attempting to perform the undo. +* Alternative 3 (Current Choice): Stored as an invalid string i.e. "" + * Pros: Avoid NullPointerExceptions + * Cons: We must ensure that the conversion from Object to Json and vice-versa must be correct. -
+### `addtg` feature -The following sequence diagram shows how the undo operation works: +The `addtg` command adds tutorial group(s) to a student -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +The *add tutorial group(s) to a student* mechanism is facilitated by the `LogicManager` and the `AddressBookParser`. It is implemented by adding the parser class `AddTutorialGroupParser` and the command class `AddTutorialGroupCommand`. -
: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. +``` +command format: addtg INDEX tg/TUTORIAL_GROUP... +``` +#### How `addtg` command is parsed and executed -
+Assuming the command is valid and execution is successful, -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. +1. `LogicManager` is called to execute the command, using the `AddressBookParser` class to parse the + command. +2. `AddressBookParser` sees that the command has the valid starting command word `addtg` and creates a + new `AddTutorialGroupParser` that parses the command. +3. `AddTutorialGroupParser` confirms the command is valid and returns a `AddTutorialGroupCommand` to + be executed by the `LogicManager` +4. `LogicManager` executes `AddTutorialGroupCommand`, which gets the relevant information from the + `Model` component, getting the filtered student list and acquiring the student at the specified `Index`. +5. `AddTutorialGroupCommand` creates a new `Student` combining the existing and newly specified `TUTORIAL_GROUP(s)` and returns the relevant `CommandResult` to `LogicManager` -
: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. +Rationale: +- A new `Student` is created with the new combined information instead of adding the new tutorial group(s) to the existing `Student` because a `Student` object is immutable. +- An `Index` based on the current list shown is used to specify which `Student` will be updated. An alternative would be to use the name of the student instead of an index. However, an index makes it easier and faster for users to key in the command as it is way shorter (length) as compared to a student's name. + - Hence, to increase efficiency of TACH, we have chosen `index` to be our indicator. -
+#### Example usage scenario and how the `addtg` mechanism behaves + +When the user executes `addtg 2 tg/CS2103T W15-3 tg/CS2101 G08` command to add a tutorial group to the 2nd student listed in the address book. + +The following sequence diagram shows how the `addtg` operation works: + + + +The following diagram shows a brief overview of the AddTutorialGroupDescriptor created shown in the `addtg` sequence diagram above + + + +Go back to **[Table of Contents](#table-of-contents)** + +### `deletetg` feature + +The `deletetg` command deletes a tutorial group from a student. + +The *deleting a tutorial group from student* mechanism is facilitated by the `LogicManger` and the +`AddressBookParser`. It is implemented by adding the parser class `DeleteTutorialGroupParser` and the +command class `DeleteTutorialGroupCommand`. + +``` +command format: deletetg INDEX tg/TUTORIAL_GROUP +``` + +#### How `deletetg` command is parsed and executed + +Assuming the command is valid and execution is successful, -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. +1. `LogicManager` is called to execute the command, using the `AddressBookParser` class to parse the +command. +2. `AddressBookParser` sees that the command has the valid starting command word `deletetg` and creates a +new `DeleteTutorialGroupParser` that parses the command. +3. `DeleteTutorialGroupParser` confirms the command is valid and returns a `DeleteTutorialGroupCommand` to +be executed by the `LogicManager` +4. `LogicManager` executes `DeleteTutorialGroupCommand`, which gets the relevant information from the +`Model` component, getting the filtered student list and acquiring the student at the specified `Index`. +5. `DeleteTutorialGroupCommand` deletes the specified `TUTORIAL_GROUP` of the student and returns the relevant `CommandResult` to `LogicManager` -![UndoRedoState4](images/UndoRedoState4.png) +Sequence Diagram: -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) +
-The following activity diagram summarizes what happens when a user executes a new command: +This feature was implemented to follow this sequence to keep it consistent with the rest of the `Command`s +and `Parser`s. - +#### A few interesting details regarding how `deletetg` command works -#### Design considerations: +- The command takes in an `Index` instead of a student's name because we felt that it was much easier to +type in a number than the entirety of someone's name. It is also distinct and much less vague. -**Aspect: How undo & redo executes:** -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +- Only one tutorial group can be deleted at a time. If a student has some tutorial groups but not all +tutorial groups to be deleted, what should the command do? Making it such that only one tutorial group can +be deleted at a time prevents ambiguity in contrast to if several tutorial groups can be deleted at a time. -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. -_{more aspects and alternatives to be added}_ +- If the tutorial group to be deleted is the only one that the student has, the command will not work. A +student must have at least one tutorial group. If this were not the case, it could result in some serious +buggy behaviours regarding other commands involving tutorial groups. -### \[Proposed\] Data archiving -_{Explain here how the data archiving feature will be implemented}_ +- The tutorial group must be typed exactly, but is case-insensitive.
An alternative would be to +indicate an `Index` instead of the exact tutorial group, but that would mean we would either have to +display an overall index of all the modules, or display an index of all the modules for each student. +Either way it would make the UI more complex and cluttered.
This is why we decided to make it such +that it must be typed exactly, but is case-insensitive, since two tutorial groups should be the same if +only their cases are different. +### `findtg` feature + +The `findtg` command will list all students in a particular tutorial group. + +The *find a tutorial group* mechanism is facilitated by the `LogicManger` and the +`AddressBookParser`. It is implemented by adding the parser class `FindTutorialGroupParser`, the +command class `FindTutorialGroupCommand` and the model class `TutorialGroupKeywordsPredicate`. + +``` +command format: findtg TUTORIAL_GROUP +``` + +#### How `findtg` command is parsed and executed + +Assuming the command is valid and execution is successful, + +1. `LogicManager` is called to execute the command, using the `AddressBookParser` class to parse the + command. +2. `AddressBookParser` sees that the command has the valid starting command word `findtg` and creates a + new `FindTutorialGroupParser` that parses the command. +3. `FindTutorialGroupParser` confirms the command is valid and returns a `FindTutorialGroupCommand` to + be executed by the `LogicManager` +4. `FindTutorialGroupCommand` passes the specified `TUTORIAL_GROUP` name to `TutorialGroupKeywordsPredicate` to +filter out all students with the specified `TUTORIAL_GROUP` name +5. `LogicManager` executes `FindTutorialGroupCommand`, which gets the relevant information from the + `Model` component, getting the filtered student list. +6. `FindTutorialGroupCommand` filter out all students in the specified `TUTORIAL_GROUP` and returns the relevant +`CommandResult` to `LogicManager` + +#### Example usage scenario and how the *findtg* mechanism behaves + +When the user executes `findtg CS2100 T05` command to find a tutorial group. + +The following sequence diagram shows how the `findtg` operation works: + + + +There are a few interesting details as to how the command works: + +- The tutorial group must be typed exactly, but is case-insensitive. Since it is more convenience for user to type and + two tutorial groups should be the same if only their cases are different. + +- If there are no matching tutorial group found in the students, the command will return an empty student list + +- `findtg` do not support find partial keyword to prevent ambiguity and TAs usually teach a single module with multiple +tutorial group. This operation is provided for users to sort students by their specified tutorial group + +Go back to **[Table of Contents](#table-of-contents)** -------------------------------------------------------------------------------------------------------------------- @@ -257,71 +437,291 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* is a Computer Science (CS) Teaching Assistant (TA) in NUS +* is teaching multiple Computer Science modules/tutorial groups +* has a need to manage a significant number of students * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: TACH helps CS Teaching Assistants teaching multiple tutorial groups to manage +their students in an organized manner. Our sorting feature will allow TAs to view, categorize +and get information of all their students at one glance. ### 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 | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can …​ | +|----------|------------------------------------------|---------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------| +| `* * *` | CS TA | add a student | keep track of them and their contacts | +| `* * *` | CS TA | add a tutorial group to a student | identify which tutorial groups a student is taking | +| `* * *` | CS TA | delete a student | make sure I have the correct student in the list | +| `* * *` | CS TA | delete a tutorial group from a student | make sure a student has the correct tutorial groups | +| `* * *` | CS TA | delete a tutorial group from all students | remove non-existing tutorial groups at the end of a semester easily | +| `* * *` | CS TA | find students by name | contact the appropriate student | +| `* * *` | Forgetful CS TA | find students by parts of their names | contact the appropriate student even if I don't remember their full names | +| `* * *` | CS TA using the application | see all the students' contact information that I stored | | +| `* * *` | CS TA with small desktop screen | see all the students' contact information that I stored | | +| `* * *` | CS TA | get my students' private contact details like their email, Telegram and GitHub easily | save time from the convenience of having all the contact details in one place | +| `* * *` | CS TA who is experienced in CLI programs | type everything in one command at one go | manage things in the application more quickly | +| `* * *` | CS TA new to the application | be able to find a user guide for the application | refer to it when needed | +| `* * *` | CS TA using the application | be able to exit the application | | +| `* * *` | CS TA | be able to edit the details of the student I added | correct my mistakes or add information without having to add a new Student | +| `* * ` | CS TA | sort my students by tutorial groups | find the appropriate students for my tutorial groups easily | +| `* * ` | CS TA | sort my students by name | easily find someone if I forgot part of their name | +| `* * ` | CS TA | find students by a tutorial group | see which students are in that tutorial group | +| `* * ` | CS TA | undo my mistakes | | +| `* *` | CS TA | redo my mistakes | | +| `* *` | CS TA | store zoom link and venue of tutorial session | find them easily when it is time for tutorial | +| `* *` | CS TA that finished a semester | clear my student contact list | easily start afresh for the next semester | +| `* *` | Forgetful TA | store timing of the tutorial session | find them easily when I lose track of time | +| `* *` | CS TA | keep track of the assignments submitted by students | mark accordingly | +| `* ` | CS TA | send group messages to a specific group of students | make announcements effectively | +| `* ` | CS TA | see the announcements that I have sent to the students in a tutorial group | | +| `* ` | Busy TA | set an alarm before the tutorial starts | be on time for tutorial session | + + +Go back to **[Table of Contents](#table-of-contents)** ### 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 `Teaching Assistant Contact Helper (TACH)` and the **Actor** is the `Teaching Assistant (TA)`, unless specified otherwise) -**Use case: Delete a person** +**Use case: UC01 - Add a student** -**MSS** +**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 adds a new student to the contact list by giving their name, their email and their tutorial group +2. Student successfully added to the list Use case ends. **Extensions** -* 2a. The list is empty. +* 1a. The command has an invalid name, email and/or tutorial group. + * 1a1. TACH prompts the TA to type in the valid parameters. + Step 1a1 is repeated until the data entered is correct. + + Use case resumes from step 2. + + +* 1b. The command does not have a name, email and/or tutorial group. + * 1b1. TACH prompts the TA to add in the valid parameters. + Step 1b1 is repeated until the data entered is correct. + + Use case resumes from step 2. +

+ +**Use case: UC02 - Add a tutorial group to a student** + +**MSS:** + +1. TA adds a new tutorial group to a student by giving the relevant tutorial group. +2. The new tutorial group is successfully added to the student. + + Use case ends. + +**Extensions** + +* 1a. The command has an invalid tutorial group. + * 1a1. TACH prompts the TA to type in a valid tutorial group. + Step 1a1 is repeated until a valid tutorial group is entered. + + Use case resumes from step 2. + +* 1b. The command has an invalid student or a student that does not exist in the contact list. + * 1b1. TACH prompts the TA to type in a valid student. + Step 1b1 is repeated until a valid student is entered. + + Use case resumes from step 2. +

+ +**Use case: UC03 - Delete a student** + +**MSS:** + +1. TA requests to view all students. +2. TACH shows a list of students. +3. TA requests to delete a specific student in the list by their index on the list. +4. TACH deletes the student from the tutorial group. + + Use case ends. + +**Extensions** + +* 1a. The student list is empty. There are no students that can be deleted. Use case ends. +* 1b. TA chooses instead to find students from a specific tutorial group. + + Use case resumes at step 2. + * 3a. The given index is invalid. + * 3a1. TACH prompts the TA to type in a valid index. + Step 3a1 is repeated until a valid index is entered. - * 3a1. AddressBook shows an error message. + Use case resumes at step 4. +

- Use case resumes at step 2. +**Use case: UC04 - Delete a tutorial group from a student** -*{More to be added}* +**MSS:** -### Non-Functional Requirements +1. TA requests to delete a tutorial group from a student. +2. The tutorial group is successfully deleted from the student. + + Use case ends. + +**Extensions** + +* 1a. The tutorial group requested is an invalid tutorial group or the student is not under that tutorial group. + * 1a1. TACH prompts the TA to type a valid tutorial group. + Step 1a1 is repeated until a valid tutorial group is entered. + +* 1b. The tutorial group requested to be deleted is the only tutorial group the student has. + * 1b1. TACH notifies the TA that the tutorial group cannot be deleted. + + Use case ends. +

+ +**Use case: UC05 - Find students from a tutorial group** + +**MSS:** + +1. TA requests to find a tutorial group. +2. TACH list out all the students from the tutorial group. + + Use case ends. + +**Extensions** + +* 1a. The tutorial group entered is not found in any student. + * 1a1. TACH prompts that there is 0 student in the list. + + Use case ends. +

+ +**Use case: UC06 - Clearing all students** + +**MSS:** + +1. TA chooses to clear all students from their contact list. +2. TACH completely clears its list. + + Use case ends. +

+ +**Use case: UC07 - Delete a tutorial group from all students** + +**MSS:** + +Similar to UC04 except that it applies to all students under that tutorial group instead. -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. +**Extensions** + +* 1a. The tutorial group requested is an invalid tutorial group. + * 1a1. TACH prompts the TA to type a valid tutorial group. +Step 1a1 is repeated until a valid tutorial group is entered. + +* 1b. The tutorial group requested to be deleted is the only tutorial group the student has. + * 1b1. TACH deletes the tutorial group from the student. + * 1b2. The student with no tutorial groups remaining afterwards will be deleted. +Steps 1b1 - 1b2 are repeated until the requested tutorial group is removed from all the students under it. +

+ +**Use case: UC08 - Get user guide** + +**MSS:** + +1. TA requests for the user guide. +2. TACH provides the link to the user guide. + + Use case ends. +

+ +**Use case: UC09 - Exit TACH** + +**MSS:** + +1. TA requests to exit TACH. +2. TACH closes. + + Use case ends. +

+ +**Use case: UC10 - See list of all students** + +**MSS:** + +1. TA request to see list of all students in TACH. +2. TACH displays the list of all students that are stored. + + Use case ends. +

+ +**Use case: UC11 - Find a student** + +**MSS:** -*{More to be added}* +1. TA requests to find a student. +2. TACH list out all the students that was requested. + + Use case ends. +

+ +**Use case: UC12 - Edit a student** + +**MSS:** + +1. TA requests to edit information of a specific student in the list by their index on the list. +2. TACH updates the student specified by the index with the information provided by TA + + Use case ends. + +**Extensions** +* 1a. The given index is invalid. + * 1a1. TACH prompts the TA to type in a valid index. + Step 1a1 is repeated until a valid index is entered. + +* 1b. The email/telegram/github/name, that is provided, is invalid. + * 1b1. TACH prompts the TA to type in a valid attribute for the attribute that is invalid. + Step 1b1 is repeated until the provided attributes are valid. + +* 1c. The tutorial group ,if provided, is empty or invalid + * 1c1. TACH prompts the TA to type in a non-empty and valid tutorial group. + Step 1c1 is repeated until the tutorial group is valid and non-empty. + Use case resumes at step 2. +

+ +Go back to **[Table of Contents](#table-of-contents)** + +### Non-Functional Requirements + +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +2. Should be able to hold up to `1000` persons without a noticeable sluggishness in performance for typical usage. +3. The system should respond within two seconds. +4. 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. +5. The user interface should be easy to understand for beginner users. +6. The commands should feel intuitive and easy to pick up and remember to a beginner user. +7. The source code should be open source. +8. The product is free and ready-to-use as soon as one downloads it. +9. The product should work offline, without an Internet connection. ### Glossary +* **Tutorial Group**: Tutorial Group is synonymous with "(Tutorial) Class", we use the term Tutorial Group in our code +and documentation to prevent it from being confused with "Java Classes". * **Mainstream OS**: Windows, Linux, Unix, OS-X * **Private contact detail**: A contact detail that is not meant to be shared with others +* **API**: An application programming interface (API) is the medium by which different software interact +* **Interface**: An abstract type that is used to specify a behavior of certain tutorial groups +* **System admin commands**: Terminal commands such as `pwd`, `ls`, `tar` +* **Open source**: Open source code is publicly accessible to everyone to read, modify and distribute -------------------------------------------------------------------------------------------------------------------- @@ -349,29 +749,74 @@ testers are expected to do more *exploratory* testing. 1. 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 student -### Deleting a person +1. Deleting a student while all students are being shown -1. Deleting a person while all persons are being shown - - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: List all students using the `list` command. Multiple students in the list. 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. + Expected: First student is deleted from the list. Details of the deleted student shown in the status message. Timestamp in the status bar is updated. 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No student is deleted. Error details shown in the status message. Status bar remains the same. 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous. -1. _{ more test cases …​ }_ - -### Saving data +### Finding a tutorial group + +1. Finding students from a particular tutorial group while all students are being shown + 1. Prerequisites: Multiple students are added to a tutorial group eg:`CS2103T W15`. + + 2. Test case: `findtg CS2103T W15`
+ Expected: all students added to the tutorial group `CS2103T W15` are listed out. Number of students listed out are shown in the status message. + + 3. Test case: `findtg CS2103T`
+ Expected: No student listed. Since user only input module code without tutorial group details. All invalid entry of tutorial group or no matching tutorial group will result in no student listed. + +### Dealing with corrupted data files + +* Troubleshooting with corrupted files: + 1. Prerequisite: Corrupted data file. + * To simulate a corrupted data file: + * Open the data folder in the folder where TACH is in. Edit addressbook.json and change one of the fields to an invalid one + e.g. Add a `!` at the end of Irfan's email. + i.e. change from + > "name" : "Irfan Ibrahim", +
"telegram" : "@irfan201", +
"email" : "irfan@hotmail.com", +
"gitHub" : "", +
"inTutorialGroups" : [ "CS2106 T01" ] + + to + > "name" : "Irfan Ibrahim", +
"telegram" : "@irfan201", +
"email" : "irfan@hotmail.com!", +
"gitHub" : "", +
"inTutorialGroups" : [ "CS2106 T01" ] + + > **NOTE:** If there is no data file, open TACH and enter the command `list`. The data file should appear in the folder where TACH is in. + 2. Test case: Corrupted data file +
Open the data folder in the folder where TACH is in. Edit all the data such that it meets the requirement stated here.[Input Requirements](https://ay2122s2-cs2103t-w15-3.github.io/tp/UserGuide.html#input-requirements) +
Expected: TACH will now load the data file and not an empty one. + * Example: To resolve the issue in step i, change Irfan's email from an invalid one (`irfan@hotmail.com!`) + to a valid one (`irfan@hotmail.com`) by removing the `!` at the end. i.e. +
change from : + > "name" : "Irfan Ibrahim", +
"telegram" : "@irfan201", +
"email" : "irfan@hotmail.com!", +
"gitHub" : "", +
"inTutorialGroups" : [ "CS2106 T01" ] + + to + > "name" : "Irfan Ibrahim", +
"telegram" : "@irfan201", +
"email" : "irfan@hotmail.com", +
"gitHub" : "", +
"inTutorialGroups" : [ "CS2106 T01" ] + +Go back to **[Table of Contents](#table-of-contents)** -1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ -1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..4c88501ad5d 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,497 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +## Introduction + +Teaching Assistant Contact Helper (TACH) is a desktop application that helps Computer Science (CS) Teaching Assistants (TAs) (like you!) +from National University of Singapore (NUS), especially those that tutor +multiple tutorial groups, by **managing their students in an organised manner**. + +You can **add, edit and delete** students and their tutorial groups from the list easily. +You can also **find** students by their name or by their tutorial group. + +TACH automatically sorts students in alphabetical order, and all the students' contact details are right next to +their names, so that you can get their details in a glance, and easily know which contacts belong to whom. + +TACH works by typing what you want to do as commands. It is optimized for keyboard users, so if you can type fast, +TACH can work even faster. + +## How to use this guide + +**The goal of this guide is to help you**, the reader, to **understand how to use our app better**, regardless of +whether you are a new user or an experienced user. + +If you are a **new user** and want to learn how to download and set up the app, go to **[Quick Start](#quick-start)**. +
If you have already installed the app and want to learn the basics, go to **[Quick Tutorial](#quick-tutorial)**. + +If you are an **experienced user** who wants to use the app to its full potential, or just want a quick +refresher on the commands, you can look at all the commands for TACH via **[Commands](#commands)**. + +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents * Table of Contents {:toc} - + -------------------------------------------------------------------------------------------------------------------- -## Quick start +## Quick Start + +1. Ensure you have **[Java 11](https://www.oracle.com/java/technologies/downloads/#java11-windows)** or above installed in your Computer. + +2. Download the latest release of `TACH.jar` **[here](https://github.com/AY2122S2-CS2103T-W15-3/tp/releases/latest)**. -1. Ensure you have Java `11` or above installed in your Computer. +3. Copy the file to the folder you want to use as the _home folder_ for your TACH. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +4. Double-click the file to start the app. The app should appear in a few seconds and look similar to the image below. Note how the app contains some sample data.

+ ![Quick Start](images/QuickStart.png) +
+
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +**:exclamation: For macOS users:**
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +If the app does not open by double-clicking, follow these steps instead: -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: +1. Move the downloaded `TACH.jar` to a new folder (e.g. TACHfolder) in your `Desktop`. +2. Open up your **terminal** application. +3. Enter `cd Desktop` in your terminal to go into your desktop directory. +4. Then, enter `java -jar TACHfolder/TACH.jar` to open the app. - * **`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. +**:information_source: Note:**
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +* You can **resize** the app by clicking and dragging the edges of the window to see more users at a time. +You can also **maximise** the window. - * **`clear`** : Deletes all contacts. +
- * **`exit`** : Exits the app. +You're ready to start using TACH! You can continue learning how to use TACH through the +**[Quick Tutorial](#quick-tutorial)**. -1. Refer to the [Features](#features) below for details of each command. +On the other hand, if you're feeling confident, you can dive straight into all the **[Commands](#commands)**. -------------------------------------------------------------------------------------------------------------------- -## Features +## Quick Tutorial + +This quick tutorial will teach you how to navigate through the app and its components, along with some basic commands +that you can use right now! + +### App Components + +Here is a quick summary of all the components of the app: +
(Remember that you can **resize the window**!) + +![Quick Tutorial Components](images/QuickTutorialComponents.png) + +- ![Options](images/IconColourOrange.png) **Options**: Various options that you can use for the app. + +- ![Command Box](images/IconColourRed.png) **Command Box**: **The place where you type your commands.** +Once you have finished typing your command, press the **⏎Enter** key on your keyboard to submit your command. + +- ![Student Box](images/IconColourCyan.png) **Student Box**: Represents a student and all their details. It has +an index, the student's name, their contact details, and their tutorial group(s). + +- ![Index](images/IconColourPink.png) **Index**: A number showing where the student is positioned in the current list. + +- ![Name](images/IconColourYellow.png) **Name**: The student's name. + +- ![Contact Details](images/IconColourGreen.png) **Contact Details**: The student's contact details. + - The student must have an ![Email](images/LogoEmail.png) **Email**, +but their ![Telegram](images/LogoTelegram.png) **Telegram** and ![GitHub](images/LogoGithub.png) **GitHub** usernames +can be optional. + +- ![Tutorial Groups](images/IconColourBlue.png) **Tutorial Group(s)**: The student's tutorial group(s). The student +must have at least one tutorial group. + +- ![Results Display](images/IconColourPurple.png) **Results Display**: Displays the results of a successful command, +or an appropriate error message if the command is invalid. + +### Quick Command Tutorial + +Now that you know what each of the components are, it's time to learn a few commands! Commands will look like +**`this`**. Type the following commands exactly into the command box and press the **⏎Enter** key to submit +your command. The effect of each command is described after the command itself. + +1. **`list`** : Lists all the students in TACH. +

+2. **`add n/Carl Sagan e/carlsagan42@gmail.com tg/cs2100 g01`** : Adds the student "Carl Sagan" with their email +and tutorial group to TACH. They should appear as the 3rd person (Index 3) on the list. +

+3. **`delete 2`**: Deletes the 2nd person in the displayed list. Here, "Bernice Yu" should be deleted +from the list. +

+4. **`find david`**: Finds all students that have "David" in their name. Only "David Li" should +be visible to you now. +

+5. **`edit 1 n/David Lee t/DavidLee777 g/david-lee`** : Edits the 1st person in the visible list by changing +their name, their Telegram and their GitHub. This should edit "David Li" to "David Lee" and add their +Telegram and GitHub usernames. +

+6. **`findtg cs2106 t05`** : Finds all students that have "CS2106 T05" as a tutorial group. In this tutorial, +you should see 3 students that have "CS2106 T05" as their tutorial group. +

+7. **`list`** : Lists all the students in TACH again. +

+8. **`deletetgall tg/cs2106 t05`** : Deletes the tutorial group "CS2106 T05" from every student. Students that +have no tutorial groups afterwards will be deleted as well. In this tutorial, you should see that "CS2106 T05" +is nowhere to be found, and the student "Charlotte Oliveiro" is deleted since they no longer have any +tutorial groups. +

+9. Experiment with some commands! Try out the same commands but with different formats, or you can take a look at +**[Commands](#commands)** for a few more commands that are not covered in this tutorial. When you're ready, +you can move on to the next two easy commands to complete the tutorial. +

+10. **`clear`** : Clears TACH of all students. +

+11. **`exit`** : Exits the app. + +This completes the command tutorial! You're now ready to start adding your own students!
-**:information_source: Notes about the command format:**
+**:information_source: Note about reloading sample students:**
+ +If you ever get stuck or want to try out the tutorial again, follow these instructions to reload the +sample students into the app. -* 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`. +1. Exit the app. +2. Go to the folder that TACH is in. +3. Delete the `data` folder. +4. Reopen the app again. -* 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`. +The app should give back the original sample students for you to try out commands again! -* 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. +
-* 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. +Congratulations! You've learned what each component in the app is, and some basic commands that you can use right now +to get started! If you want more details on what each command does, you can refer to **[Commands](#commands)** for +a better understanding.

-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. -* 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`. +Go back to **[Table of Contents](#table-of-contents)**. + +-------------------------------------------------------------------------------------------------------------------- + +## Commands + +List of commands: +* [Viewing help](#viewing-help--help) +* [Listing students](#listing-all-students-list) +* [Adding](#adding) + - [Adding a student](#adding-a-student-add) + - [Adding a tutorial group](#adding-a-tutorial-group-for-a-student-addtg) +* [Editing a student](#editing-a-student--edit) +* [Finding](#finding) + - [Finding students by name](#finding-students-by-name-find) + - [Findind students by tutorial group](#finding-students-by-tutorial-group-findtg) +* [Deleting](#deleting) + - [Deleting a student](#deleting-a-student-delete) + - [Deleting a tutorial group from a student](#deleting-a-tutorial-group-from-a-student-deletetg) + - [Deleting a tutorial group from all students](#deleting-tutorial-groups-from-all-students-deletetgall) +* [Clearing all entries](#clearing-all-entries--clear) +* [Exiting the program](#exiting-the-program--exit) + +
+ +**:information_source: Notes about the command format:** + +* Words in `UPPER_CASE` are the parameters to be supplied by you.
+ e.g. in `add n/NAME`, `NAME` is a parameter which can be used like `add n/John Doe` for example.
+ + +* Items in square brackets are optional.
+ e.g. `e/EMAIL [t/TELEGRAM]` can be used as `e/e0123456@u.nus.edu t/JohnSmith` or as `e/e0123456@u.nus.edu`

+ +* Items with `…` after them can be added multiple times.
+ e.g. `tg/TUTORIAL_GROUP…` can be used as `tg/CS2103 W15-3`, `tg/CS2103 W15-3 tg/CS2100 G08` etc.

+ +* Parameters can be in any order.
+ e.g. if the command specifies `n/NAME e/EMAIL`, `e/EMAIL n/NAME` is also acceptable.

+ +* Extraneous parameters for commands that do not take in parameters (such as `help`) will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+ - `help123` will not be interpreted as `help`.

+ +* For information on the requirements of the different inputs, refer to [Input Requirements](#input-requirements).
### Viewing help : `help` -Shows a message explaning how to access the help page. - -![help message](images/helpMessage.png) +Opens a window showing the **[Command Summary](#command-summary)** and the **[Input Requirements](#input-requirements)**, +and also gives a reference to the full user guide, which is this page. Format: `help` +![Help Window](images/HelpWindow.png) -### Adding a person: `add` +### Listing all students: `list` -Adds a person to the address book. +Shows a list of all students in TACH. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: `list` -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+![Results Display](images/ListCommandResult.png) + +The students will be sorted in alphabetical order by their names. + +### Adding + +#### Adding a student: `add` + +Adds a student into TACH. + +Format: `add n/NAME e/EMAIL [t/TELEGRAM] [g/GITHUB] tg/TUTORIAL_GROUP…` + +Note: +* Adding a student with the exact same name as another student in TACH is **not allowed**. + - e.g. If `Sam` exists in TACH, another `Sam` **cannot be added again** even if their contact details are different. + - You can manually differentiate them by saving them as `Sam 1` and `Sam 2` for example, or use your own way + of identifying them. +* Names in TACH are used to uniquely identify students. If two students who have the same name are in the same +tutorial group, it would be difficult to differentiate the two students. 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 tg/CS2100 G08 e/e0123456@u.nus.edu` +* `add n/Michael Tay e/michaelT@gmail.com t/MichaelTay g/michael777 tg/CS2103T W15-3 tg/CS2100 G08` -### Listing all persons : `list` +The following image shows the output for the following command:
+`add n/Chloe Lee e/chloexlee@gmail.com t/chloe201 g/jchloechloe tg/CS2101 G08` -Shows a list of all persons in the address book. +![Results Display](images/AddStudentResult.png)

-Format: `list` +#### Adding tutorial group(s) for a student: `addtg` + +Adds tutorial group(s) for a student in TACH. + +Format: `addtg INDEX tg/TUTORIAL_GROUP…` + +* Adds one or more tutorial groups for the student at the specified `INDEX`. +* Adding of tutorial groups is cumulative. (Existing tutorial groups of the specified student will +remain unchanged.) + +Example: +* `list` followed by `addtg 2 tg/CS2040S T03` adds the tutorial group `CS2040S T03` for the 2nd student listed in TACH. +* `find Dave` followed by `addtg 1 tg/CS2040S T03 tg/CS3230 T01` adds the tutorial groups `CS2040S T03` and +`CS3230 T01` for the 1st student in the results of the `find` command. + +### Editing a student : `edit` -### Editing a person : `edit` +Edits an existing student in TACH. -Edits an existing person in the address book. +Format: `edit INDEX [n/NAME] [e/EMAIL] [t/TELEGRAM] [g/GITHUB] [tg/TUTORIAL_GROUPS]…` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +* Edits the student at the specified `INDEX` +* At least one of the optional details (Name/NUS email/Telegram/GitHub/Tutorial Groups) must be provided. +* Any details entered in the `edit` command will replace the original details of the student. +* Details not entered in the `edit` command will stay the same and not be replaced. -* 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. +>*Note*: +> * Tutorial groups **CANNOT** be empty. +`edit 1 tg/` is **NOT** allowed. +> * GitHub and Telegram **CAN** be empty. +`edit 1 g/ t/` is allowed and will set the student to have no GitHub and Telegram. 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. +* `list` followed by `edit 2 t/DaveHunter g/` edits the 2nd student listed in TACH. Their Telegram will be +edited to `DaveHunter` and they will not have any GitHub username. +* `find Robert` followed by `edit 1 n/Bobby Smiles` edits the 1st student in the results of the `find` command. Their +name will be edited to `Bobby Smiles`. -### Locating persons by name: `find` +The following diagram shows the output for `edit 5 t/chloe201 g/chloechloe` +from the previous `Add student` [command](#adding-a-student-add) -Finds persons whose names contain any of the given keywords. +![Results Display](images/EditingResult.png) -Format: `find KEYWORD [MORE_KEYWORDS]` +### Finding + +#### Finding students by name: `find` + +Finds students whose names contain all the given keywords. + +Format `find KEYWORD [ADDTIONAL_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` +* The search ignores cases. e.g. `charles` will match `Charles` +* The order of the keywords matters. e.g. `Charles Boyle` will not match `Boyle Charles` +* Keywords can match the name partially e.g. `Char` **WILL** match `Charles` + - e.g. `Charles Bo` and `les Boyle` will match `Charles Boyle`, but `Charle Boyle` will not match `Charles Boyle` +* The positions of the spaces matter. e.g. `Alex Lee En` **will not** match `Alex Leeen` +* Students whose names contain the keyword fully or partially will be returned + - e.g. `find char` will return `Charles Alex Lee`, `Charles Boyle`, and `Racharl Tan` if they are in the list of students -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +#### Finding students by tutorial group: `findtg` + +Finds all students in a particular tutorial group. -### Deleting a person : `delete` +Format `findtg TUTORIAL_GROUP` -Deletes the specified person from the address book. +* The search ignores cases. e.g. `cs2101 g08` will match `CS2101 G08` +* The search ignores extra spaces between the module name and the tutorial group name. e.g. `cs2101<><><>g08` will match `cs2101<>g08` + - In this example, `<>` means a space. + - However, e.g. `cs2101<>g<>08` **will not** match `cs2101<>g08` +* Only the tutorial group is searched. +* Only the exact tutorial group will be matched e.g. `CS2101 G` **WILL NOT** match `CS2101 G08` +* Students with matching tutorial group will be returned. e.g. `findtg CS2101 G08` will return +`Charles Martinet` and `Susan Boyle` only if both of them are in the tutorial group `CS2101 G08` +* If there are no students with matching tutorial group, no student will be returned and an empty list is displayed. + +The following diagram shows the result for `findtg CS2100 T05` + +![Results Display](images/FindTgResult.png) + +### Deleting + +#### Deleting a student: `delete` + +Deletes the specified student from TACH. 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, …​ +* Deletes the student at the specified `INDEX`. Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +* `list` followed by `delete 2` deletes the 2nd student listed in TACH. +* `find Waldo` followed by `delete 1` deletes the 1st student in the results of the `find` command. +

+ +#### Deleting a tutorial group from a student: `deletetg` + +Deletes the specified tutorial group from the specified student. + +Format: `deletetg INDEX tg/TUTORIAL_GROUP` + +* Deletes the specified tutorial group of the student at the specified `INDEX`. +* The tutorial group must be written **EXACTLY**, but *ignores cases*. e.g. `deletetg 1 tg/cs2040s t03` will +delete `CS2040S T03` if that person has that tutorial group, but `deletetg 1 tg/cs2040s` or `deletetg 1 tg/cs2040st03` +**WILL NOT** successfully delete it. +* The tutorial group **cannot be deleted** if it is the **only** tutorial group a student has. e.g. A student with only +the tutorial group `CS2040S T03` cannot have that tutorial group deleted. + +Examples: +* `list` followed by `deletetg 2 tg/CS2103T W15-3` deletes the tutorial group `CS2103T W15-3` of the 2nd student listed +in TACH (only if the 2nd student has more than one tutorial group). +* `find Carmen` followed by `deletetg 1 tg/cs2100 g01` deletes the tutorial group `CS2100 G01` of the 1st student in the +results of the `find` command (only if the 1st student has more than one tutorial group). + +#### Deleting tutorial groups from all students: `deletetgall` + +Deletes the specified tutorial group from **ALL** students in TACH. + +Format: `deletetgall tg/TUTORIAL_GROUP…` + +* Deletes the specified tutorial group from **ALL** students stored in TACH. +* The tutorial group must be written **EXACTLY**, but *ignores cases*. e.g. `deletetgall tg/cs2040s t03` will + delete `CS2040S T03` from tutorial groups of all Students, but `deletetgall tg/cs2040s` or `deletetgall tg/cs2040st03` **WILL NOT** successfully delete `CS2040S T03` from students in TACH. +> **NOTE**: After the deletion, you will see all the updated remaining students in TACH. + +>**WARNING:** +> 1. Students with 0 tutorial groups after the deletion will automatically be deleted. +> 2. **ALL** students in TACH will be affected. Even those not visible as a result of the `find` command. + +Example: +* `find Eve` followed by `deletetgall tg/CS2103T W15-3` deletes the tutorial group `CS2103T W15-3` from all students in TACH, including those not visible after the `find Eve` command (i.e. students whose name does not contain `Eve`) +* `deletetgall tg/CS2106 T08 tg/CS2103T W13-3` will remove tutorial groups `CS2106 T08` and `CS2103T W13-3` from all students in TACH. Students with 0 tutorial groups after the tutorial groups' deletion will be deleted from TACH. + +The following diagram shows the output of `deletetgall tg/CS2100 T05` +from the previous `findtg` [command](#finding-students-by-tutorial-group-findtg) + +![Results Display](images/DeleteTgAllResult.png)
+ +Note: +- 3 students, `Gwak Seo Hyeon`, `John Smith` and `Pranith Loganathan` are deleted because they have 0 tutorial groups left after `CS2100 T05` is deleted. ### Clearing all entries : `clear` -Clears all entries from the address book. +Clears all entries from TACH. This means every student and all their details. Format: `clear` +>**WARNING:** +> This action erases all the student data stored in your computer and CANNOT be undone! Consider carefully before calling the command as you will have to re-enter the students' details if clear is executed accidentally. + ### Exiting the program : `exit` Exits the program. Format: `exit` -### Saving the data +## Input Requirements -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +There are **parameters** (like Names, NUS Emails, and GitHub usernames for example) that must follow certain +requirements so that TACH recognises them as valid parameters. Here is a list of requirements of every parameter to +easier understand which parameters are invalid when typing a command. -### Editing the data file +In the list, a **word** is defined as a bunch of *characters* (letters, numbers, punctuation, etc.) separated by spaces. +e.g. `There A_RE 4 w0-rd_s.` has 4 words. -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +| Parameter | Requirements | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **INDEX** | Must be a positive number that can be a maximum of 2,147,483,647 (1, 2, 3, … , 2147483647).
e.g. `100` | +| **NAME** | Must contain letters. Can contain spaces, apostrophes and hyphens as long as they are in between letters. Consecutive spaces, apostrophes or hyphens are not allowed. Name must start with a letter. Numbers are allowed, but they must strictly be at the end of the name separated by a space. Cannot be empty, and must not be more than 100 characters long, including spaces.
e.g. `John Smith 2` or `Johnson-Johnson Maxine d'Arby` | +| **TUTORIAL_GROUP** | Must **STRICTLY** consist of a module code, followed by a space, then the tutorial name. It should not be blank. The tutorial name must have a non-zero digit and cannot start or end with a hyphen. The name can consist of a combination of letters, hyphens, digits or underscores. Tutorial groups can only have a maximum of 100 characters including spaces.
e.g. `CS2103T W15-3_A`

**ALL** letters in tutorial group inputs will be converted into uppercase letters
e.g. `addtg 2 tg/cs2101 t01` will add `CS2101 T01` into the list of tutorial groups of student 2, assuming there are at least 2 students in the list that you see. | +| **EMAIL** | An email consists of three parts in the following order, the local part, the `@` sign, and the domain part.
The local part should only contain letters, numbers, and these special symbols: `+` `_` `.` `-`. It cannot start or end with the special symbols. It must be at most 64 characters long.
There must be an @ sign afterwards, followed by the domain name.
The domain name must be one of the following: `u.nus.edu` `nus.edu.sg` `gmail.com` `yahoo.com` `outlook.com` `hotmail.com`
e.g. `e0123456@u.nus.edu` or `jasminelim@gmail.com`
(Our original intention is for you to put in your students' NUS emails which are given to you on LumiNUS, but we also provide the freedom to use some of the more popular email providers listed above, should they be more convenient.) | +| **TELEGRAM** | May include `@` at the start. Must be exactly one word that can contain letters, numbers and underscores. It must be between 5 to 32 characters long (inclusive). This does not count the `@` symbol.
e.g. `Dave3` or `@Lorem_ipsum_dolor_sit_amet_12345` | +| **GITHUB** | Must be exactly one word that can contain letters, numbers and hyphens. It must be between 2 to 39 characters long (inclusive).
e.g. `12345678` or `cake-is-a-lie77` | -
: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. -
+## Saving the data + +TACH data is saved in your computer's [hard disk](#glossary) automatically after any command that changes the data is entered. There is no need to save manually. + +## Editing the data file + +TACH data is saved as a [JSON file](#glossary) under `[TACH file location]/data/addressbook.json`.
+**You should not edit the data file directly unless you are absolutely sure of what you are doing!**
+Only advanced users who are absolutely familiar with the data format should try updating the data directly by editing the data file. -### Archiving data files `[coming in v2.0]` +>
:exclamation: Caution: +>If your changes to the data file make its format invalid, TACH will consider the data corrupted and discard ALL data, starting with an empty data file in the next run. +>This action CANNOT be undone! Please consider making a copy of the data first before attempting to edit the data file. +>
-_Details coming soon ..._ +Go back to **[Table of Contents](#table-of-contents)**. -------------------------------------------------------------------------------------------------------------------- ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains +the data of your previous TACH home folder. + +**Q**: Does TACH works on both MacOS and Microsoft Windows?
+**A**: Yes. It is supported on both systems. + +**Q**: TACH doesn't seem to work on my computer, how may I get help?
+**A**: You may drop us an email here with the relevant questions: **[Link](mailto:vanessakhor19@gmail.com)**.

+ +-------------------------------------------------------------------------------------------------------------------- +## Glossary + +| Terms | Definition | +|---------------|-----------------------------------------------------------------------------------------------------------------| +| **Hard disk** | A storage device for data in your computer. | +| **JSON file** | A file that uses human-readable text to store and transmit data objects such as `Students` in the case of TACH. | -------------------------------------------------------------------------------------------------------------------- -## 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` +## Command Summary + +| Action | Format, Examples | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Add Student** | `add n/NAME e/EMAIL [t/TELEGRAM] [g/GITHUB] tg/TUTORIAL_GROUP…`
e.g. `add n/John Smith tg/CS2103T W15-3 e/e0123456@u.nus.edu t/johnsmyname g/johnsmyname` | +| **Add Tutorial Group for Student** | `addtg INDEX tg/TUTORIAL_GROUP…`
e.g. `addtg 5 tg/CS2100 G08` | +| **Edit Student** | `edit INDEX [n/NAME] [e/EMAIL] [t/TELEGRAM] [g/GITHUB] [tg/TUTORIAL_GROUP]…`
e.g. `edit 3 n/Mary Sue t/PresentPerfect` | +| **Find Students by name** | `find KEYWORD [ADDTIONAL_KEYWORDS]`
e.g. `find Jack Ho` | +| **Find Students by Tutorial Group** | `findtg TUTORIAL_GROUP`
e.g. `findtg CS2101 G08` | +| **Delete Student** | `delete INDEX`
e.g. `delete 4` | +| **Deleting Tutorial Group from Student** | `deletetg INDEX tg/TUTORIAL_GROUP`
e.g. `deletetg 4 tg/cs2030s t11` | +| **Deleting Tutorial Group from all Students** | `deletetgall tg/TUTORIAL_GROUP…`
e.g. `deletetgall tg/CS2106 T08` | +| **List** | `list` | +| **Clear** | `clear` | +| **Help** | `help` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..9f61f04e59c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TACH" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S2-CS2103T-W15-3/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..8e5e75a7d89 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "TACH"; font-size: 32px; } } diff --git a/docs/diagrams/AddStudentSequenceDiagram.puml b/docs/diagrams/AddStudentSequenceDiagram.puml new file mode 100644 index 00000000000..aa303868dce --- /dev/null +++ b/docs/diagrams/AddStudentSequenceDiagram.puml @@ -0,0 +1,61 @@ +@startuml +!include style.puml + +box add LOGIC_COLOR_T1 +participant ":AddStudentCommandParser" as AddStudentCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "s:Student" as Student LOGIC_COLOR +participant ":AddStudentCommand" as AddStudentCommand LOGIC_COLOR +end box + +-> AddStudentCommandParser : parse("n/john smith e/johnsmith@gmail.com t/john1 tg/CS2106 T04") +activate AddStudentCommandParser + +AddStudentCommandParser -> ParserUtil : parseName("john smith") +activate ParserUtil + +ParserUtil --> AddStudentCommandParser : n +deactivate ParserUtil + +AddStudentCommandParser -> ParserUtil : parseEmail("johnsmith@gmail.com") +activate ParserUtil + +ParserUtil --> AddStudentCommandParser : e +deactivate ParserUtil + +AddStudentCommandParser -> ParserUtil : parseTelegram("john1") +activate ParserUtil + +ParserUtil --> AddStudentCommandParser : t +deactivate ParserUtil + +AddStudentCommandParser -> ParserUtil : parseGitHub(null) +activate ParserUtil + +ParserUtil --> AddStudentCommandParser : g +deactivate ParserUtil + +AddStudentCommandParser -> ParserUtil : parseTutorialGroup("CS2106 T04") +activate ParserUtil + +ParserUtil --> AddStudentCommandParser : tg +deactivate ParserUtil + +create Student +AddStudentCommandParser -> Student : new Student(n, e, t, g, tg) +activate Student + +Student --> AddStudentCommandParser : s +deactivate Student + +create AddStudentCommand +AddStudentCommandParser -> AddStudentCommand : new AddStudentCommand(s) +activate AddStudentCommand + +AddStudentCommand --> AddStudentCommandParser +deactivate AddStudentCommand + +[<--AddStudentCommandParser +deactivate AddStudentCommandParser +destroy AddStudentCommandParser +@enduml diff --git a/docs/diagrams/AddTutorialGroupDescriptorDiagram.puml b/docs/diagrams/AddTutorialGroupDescriptorDiagram.puml new file mode 100644 index 00000000000..b211653aa0e --- /dev/null +++ b/docs/diagrams/AddTutorialGroupDescriptorDiagram.puml @@ -0,0 +1,13 @@ +@startuml +'https://plantuml.com/object-diagram' + +object "__:AddTutorialGroupDescriptor__" as addTutorialGroupDescriptor +object "__:TutorialGroup__" as tutorialGroup_1 +object "__:TutorialGroup__" as tutorialGroup_2 + +tutorialGroup_1 : tutorialGroupName = "CS2103T W15-3" +tutorialGroup_2 : tutorialGroupName = "CS2101 G08" + +addTutorialGroupDescriptor --> tutorialGroup_1 +addTutorialGroupDescriptor --> tutorialGroup_2 +@enduml diff --git a/docs/diagrams/AddTutorialGroupSequenceDiagram.puml b/docs/diagrams/AddTutorialGroupSequenceDiagram.puml new file mode 100644 index 00000000000..3f44aae90b9 --- /dev/null +++ b/docs/diagrams/AddTutorialGroupSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +!include Style.puml + +box addtg LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddTutorialGroupParser" as AddTutorialGroupParser LOGIC_COLOR +participant ":AddTutorialGroupDescriptor" as AddTutorialGroupDescriptor LOGIC_COLOR +participant ":AddTutorialGroupCommand" as AddTutorialGroupCommand LOGIC_COLOR +participant ":Student" as Student LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + -> LogicManager : execute("addtg 2 tg/CS2103T W15-3 tg/CS2101 G08") +activate LogicManager +LogicManager -> AddressBookParser : parseCommand("addtg 2 tg/CS2103T W15-3 tg/CS2101 G08") +activate AddressBookParser + +create AddTutorialGroupParser +AddressBookParser -> AddTutorialGroupParser +activate AddTutorialGroupParser +AddTutorialGroupParser --> AddressBookParser +deactivate AddTutorialGroupParser + +AddressBookParser -> AddTutorialGroupParser : parse("2 tg/CS2103T W15-3 tg/CS2101 G08") +activate AddTutorialGroupParser + +create AddTutorialGroupDescriptor +AddTutorialGroupParser -> AddTutorialGroupDescriptor : new AddTutorialGroupDescriptor() +activate AddTutorialGroupDescriptor + +AddTutorialGroupDescriptor --> AddTutorialGroupParser +deactivate AddTutorialGroupDescriptor + +AddTutorialGroupParser -> AddTutorialGroupDescriptor : setTutorialGroups() +activate AddTutorialGroupDescriptor + +AddTutorialGroupDescriptor --> AddTutorialGroupParser +deactivate AddTutorialGroupDescriptor + +create AddTutorialGroupCommand +AddTutorialGroupParser -> AddTutorialGroupCommand : new AddTutorialGroupCommand(index, addTutorialGroupDescriptor) +activate AddTutorialGroupCommand + +AddTutorialGroupCommand --> AddTutorialGroupParser +deactivate AddTutorialGroupCommand + +AddTutorialGroupParser --> AddressBookParser +deactivate AddTutorialGroupParser + +AddressBookParser --> LogicManager +deactivate AddressBookParser + +LogicManager -> AddTutorialGroupCommand : execute() +activate AddTutorialGroupCommand + +create Student +AddTutorialGroupCommand -> Student : new Student(n, t, e, g, tg) +activate Student + +Student --> AddTutorialGroupCommand +deactivate Student + +AddTutorialGroupCommand -> Model : setStudent(studentToEdit, updatedStudent) +activate Model +Model --> AddTutorialGroupCommand +deactivate Model + +AddTutorialGroupCommand -> Model : getFilteredStudentList() +activate Model +Model --> AddTutorialGroupCommand +deactivate Model + +create CommandResult +AddTutorialGroupCommand -> CommandResult +activate CommandResult + +CommandResult --> AddTutorialGroupCommand +deactivate CommandResult + +AddTutorialGroupCommand --> LogicManager +deactivate AddTutorialGroupCommand +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..d43a4bc258c 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -13,7 +13,7 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteStudent(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..6565b1f2a94 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,18 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +AddressBook *-right-> "1" UniqueStudentList +AddressBook *-right-> "1" UniqueTutorialList +UniqueTutorialList -[hidden]down- UniqueStudentList +UniqueTutorialList -[hidden]down- UniqueStudentList -UniqueTagList *-right-> "*" Tag -UniquePersonList -right-> Person +UniqueTutorialList *-right-> "*" TutorialGroup +UniqueStudentList -right-> "*" Student -Person -up-> "*" Tag +Student -up-> "1..*" TutorialGroup -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Student *--> "1" Name +Student *--> "0..1" Telegram +Student *--> "1" Email +Student *--> "0..1" GitHub @enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..577458fe4ee 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -48,7 +48,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deleteStudent(1) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/DeleteTutorialGroupSequenceDiagram.puml b/docs/diagrams/DeleteTutorialGroupSequenceDiagram.puml new file mode 100644 index 00000000000..4fe16faa0b9 --- /dev/null +++ b/docs/diagrams/DeleteTutorialGroupSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteTutorialGroupParser" as DeleteTutorialGroupParser LOGIC_COLOR +participant "d:DeleteTutorialGroupCommand" as DeleteTutorialGroupCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager: execute("deletetg 2 tg/CS2100 T01")] +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("deletetg 2 tg/CS2100 T01") +activate AddressBookParser + +create DeleteTutorialGroupParser +AddressBookParser -> DeleteTutorialGroupParser +activate DeleteTutorialGroupParser + +DeleteTutorialGroupParser --> AddressBookParser +deactivate DeleteTutorialGroupParser + +AddressBookParser -> DeleteTutorialGroupParser : parse("2 tg/CS2100 T01") +activate DeleteTutorialGroupParser + +create DeleteTutorialGroupCommand +DeleteTutorialGroupParser -> DeleteTutorialGroupCommand +activate DeleteTutorialGroupCommand + +DeleteTutorialGroupCommand --> DeleteTutorialGroupParser : d +deactivate DeleteTutorialGroupCommand + +DeleteTutorialGroupParser --> AddressBookParser : d +deactivate DeleteTutorialGroupParser +DeleteTutorialGroupParser -[hidden]-> AddressBookParser +destroy DeleteTutorialGroupParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteTutorialGroupCommand : execute() +activate DeleteTutorialGroupCommand + +DeleteTutorialGroupCommand -> Model : getFilteredStudentList() +activate Model + +Model --> DeleteTutorialGroupCommand +deactivate Model + +DeleteTutorialGroupCommand -> Model : setStudent() +activate Model + +Model --> DeleteTutorialGroupCommand +deactivate Model + +create CommandResult +DeleteTutorialGroupCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteTutorialGroupCommand +deactivate CommandResult + +DeleteTutorialGroupCommand --> LogicManager : result +deactivate DeleteTutorialGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindTutorialGroupSequenceDiagram.puml b/docs/diagrams/FindTutorialGroupSequenceDiagram.puml new file mode 100644 index 00000000000..fd254f91dc4 --- /dev/null +++ b/docs/diagrams/FindTutorialGroupSequenceDiagram.puml @@ -0,0 +1,78 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindTutorialGroupParser" as FindTutorialGroupParser LOGIC_COLOR +participant ":FindTutorialGroupCommand" as FindTutorialGroupCommand LOGIC_COLOR +participant ":TutorialGroupKeywordsPredicate" as TutorialGroupKeywordsPredicate LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager: execute("findtg CS2100 T05")] +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("findtg CS2100 T05"") +activate AddressBookParser + +create FindTutorialGroupParser +AddressBookParser -> FindTutorialGroupParser +activate FindTutorialGroupParser + +FindTutorialGroupParser --> AddressBookParser +deactivate FindTutorialGroupParser + +AddressBookParser -> FindTutorialGroupParser : parse("CS2100 T05") +activate FindTutorialGroupParser + +create FindTutorialGroupCommand +FindTutorialGroupParser -> FindTutorialGroupCommand : +activate FindTutorialGroupCommand + +create TutorialGroupKeywordsPredicate +FindTutorialGroupCommand -> TutorialGroupKeywordsPredicate : CS2100 T05 +activate TutorialGroupKeywordsPredicate + +TutorialGroupKeywordsPredicate --> FindTutorialGroupCommand : +deactivate TutorialGroupKeywordsPredicate + +FindTutorialGroupCommand --> FindTutorialGroupParser : +deactivate FindTutorialGroupCommand + +FindTutorialGroupParser --> AddressBookParser : +deactivate FindTutorialGroupParser +FindTutorialGroupParser -[hidden]-> AddressBookParser +destroy FindTutorialGroupParser + +AddressBookParser --> LogicManager : +deactivate AddressBookParser + +LogicManager -> FindTutorialGroupCommand : execute() +activate FindTutorialGroupCommand + +FindTutorialGroupCommand -> Model : getFilteredStudentList() +activate Model + +Model --> FindTutorialGroupCommand +deactivate Model + + + +create CommandResult +FindTutorialGroupCommand -> CommandResult +activate CommandResult + +CommandResult --> FindTutorialGroupCommand +deactivate CommandResult + +FindTutorialGroupCommand --> LogicManager : result +deactivate FindTutorialGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..cb676a445de 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -12,13 +12,13 @@ Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person -Class Address +Class UniqueStudentList +Class Student +Class GitHub Class Email Class Name -Class Phone -Class Tag +Class Telegram +Class TutorialGroup } @@ -34,17 +34,17 @@ ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" UniqueStudentList +UniqueStudentList --> "~* all" Student +Student *--> "1" Name +Student *--> "0..1" Telegram +Student *--> "1" Email +Student *--> "0..1" GitHub +Student *--> "1..*" TutorialGroup -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Name -[hidden]right-> Telegram +Telegram -[hidden]right-> GitHub +GitHub -[hidden]right-> Email -ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Student @enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..f7ccdf8c964 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,8 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedStudent +Class JsonAdaptedTutorialGroup } } @@ -37,7 +37,7 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedStudent +JsonAdaptedStudent --> "1..*" JsonAdaptedTutorialGroup @enduml diff --git a/docs/diagrams/StudentWithEmptyTelegramAndGitHub.puml b/docs/diagrams/StudentWithEmptyTelegramAndGitHub.puml new file mode 100644 index 00000000000..d7f6fb84cc8 --- /dev/null +++ b/docs/diagrams/StudentWithEmptyTelegramAndGitHub.puml @@ -0,0 +1,22 @@ +@startuml +'https://plantuml.com/object-diagram + +object "__:Student__" as student +object "__:GitHub__" as gitHub +object "__:Telegram__" as telegram +object "__:Name__" as name +object "__:Tutorial Group__" as tg +object "__:Email__" as email + +gitHub : value = "" +telegram : value = "" +name : fullName = "john smith" +tg : tutorialGroupName = "CS2103 WT13-2" +email : value = example@u.nus.edu + +student --> tg +student --> name +student --> gitHub +student --> telegram +student --> email +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..927a6342276 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,8 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class StudentListPanel +Class StudentCard Class StatusBarFooter Class CommandBox } @@ -32,26 +32,26 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" StudentListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +StudentListPanel -down-> "*" StudentCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +StudentListPanel --|> UiPart +StudentCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +StudentCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +StudentListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..a0ccd45a491 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFf Pointer -up-> State2 @end diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..56fc24eadcd 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editStudentDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/diagrams/tracing/Style.puml b/docs/diagrams/tracing/Style.puml new file mode 100644 index 00000000000..a08bc22c026 --- /dev/null +++ b/docs/diagrams/tracing/Style.puml @@ -0,0 +1,5 @@ +!define LOGIC_COLOR #3333C4 +!define LOGIC_COLOR_T1 #7777DB +!define LOGIC_COLOR_T2 #5252CE +!define LOGIC_COLOR_T3 #1616B0 +!define LOGIC_COLOR_T4 #101086 diff --git a/docs/images/AddStudentResult.png b/docs/images/AddStudentResult.png new file mode 100644 index 00000000000..79a82e6eeb3 Binary files /dev/null and b/docs/images/AddStudentResult.png differ diff --git a/docs/images/AddStudentSequenceDiagram.png b/docs/images/AddStudentSequenceDiagram.png new file mode 100644 index 00000000000..43efeaeafec Binary files /dev/null and b/docs/images/AddStudentSequenceDiagram.png differ diff --git a/docs/images/AddTutorialGroupDescriptorDiagram.png b/docs/images/AddTutorialGroupDescriptorDiagram.png new file mode 100644 index 00000000000..79d39783b9a Binary files /dev/null and b/docs/images/AddTutorialGroupDescriptorDiagram.png differ diff --git a/docs/images/AddTutorialGroupSequenceDiagram.png b/docs/images/AddTutorialGroupSequenceDiagram.png new file mode 100644 index 00000000000..c6fc4d6c076 Binary files /dev/null and b/docs/images/AddTutorialGroupSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..6c9361462c9 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 1ec62caa2a5..d882b6525f0 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..dfff7539424 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DeleteTgAllResult.png b/docs/images/DeleteTgAllResult.png new file mode 100644 index 00000000000..f67b1325fa3 Binary files /dev/null and b/docs/images/DeleteTgAllResult.png differ diff --git a/docs/images/DeleteTutorialGroupSequenceDiagram.png b/docs/images/DeleteTutorialGroupSequenceDiagram.png new file mode 100644 index 00000000000..940da66a5b1 Binary files /dev/null and b/docs/images/DeleteTutorialGroupSequenceDiagram.png differ diff --git a/docs/images/EditingResult.png b/docs/images/EditingResult.png new file mode 100644 index 00000000000..66636542a63 Binary files /dev/null and b/docs/images/EditingResult.png differ diff --git a/docs/images/FindTgResult.png b/docs/images/FindTgResult.png new file mode 100644 index 00000000000..189b727b5d6 Binary files /dev/null and b/docs/images/FindTgResult.png differ diff --git a/docs/images/FindTutorialGroupSequenceDiagram.png b/docs/images/FindTutorialGroupSequenceDiagram.png new file mode 100644 index 00000000000..a08c996bd68 Binary files /dev/null and b/docs/images/FindTutorialGroupSequenceDiagram.png differ diff --git a/docs/images/HelpWindow.png b/docs/images/HelpWindow.png new file mode 100644 index 00000000000..6dd9c30fed8 Binary files /dev/null and b/docs/images/HelpWindow.png differ diff --git a/docs/images/IconColourBlue.png b/docs/images/IconColourBlue.png new file mode 100644 index 00000000000..9711e20b8cf Binary files /dev/null and b/docs/images/IconColourBlue.png differ diff --git a/docs/images/IconColourCyan.png b/docs/images/IconColourCyan.png new file mode 100644 index 00000000000..3c4ae4d609b Binary files /dev/null and b/docs/images/IconColourCyan.png differ diff --git a/docs/images/IconColourGreen.png b/docs/images/IconColourGreen.png new file mode 100644 index 00000000000..7cb3b64e202 Binary files /dev/null and b/docs/images/IconColourGreen.png differ diff --git a/docs/images/IconColourOrange.png b/docs/images/IconColourOrange.png new file mode 100644 index 00000000000..21504de56c8 Binary files /dev/null and b/docs/images/IconColourOrange.png differ diff --git a/docs/images/IconColourPink.png b/docs/images/IconColourPink.png new file mode 100644 index 00000000000..3fbbd71fc95 Binary files /dev/null and b/docs/images/IconColourPink.png differ diff --git a/docs/images/IconColourPurple.png b/docs/images/IconColourPurple.png new file mode 100644 index 00000000000..3c2bc0163aa Binary files /dev/null and b/docs/images/IconColourPurple.png differ diff --git a/docs/images/IconColourRed.png b/docs/images/IconColourRed.png new file mode 100644 index 00000000000..46ace3fd5eb Binary files /dev/null and b/docs/images/IconColourRed.png differ diff --git a/docs/images/IconColourYellow.png b/docs/images/IconColourYellow.png new file mode 100644 index 00000000000..47d1dab2811 Binary files /dev/null and b/docs/images/IconColourYellow.png differ diff --git a/docs/images/ListAfterDeleteTgAll.png b/docs/images/ListAfterDeleteTgAll.png new file mode 100644 index 00000000000..a97c8b3bca1 Binary files /dev/null and b/docs/images/ListAfterDeleteTgAll.png differ diff --git a/docs/images/ListCommandResult.png b/docs/images/ListCommandResult.png new file mode 100644 index 00000000000..94ada15b8e8 Binary files /dev/null and b/docs/images/ListCommandResult.png differ diff --git a/docs/images/LogoEmail.png b/docs/images/LogoEmail.png new file mode 100644 index 00000000000..bc8069efc4c Binary files /dev/null and b/docs/images/LogoEmail.png differ diff --git a/docs/images/LogoGithub.png b/docs/images/LogoGithub.png new file mode 100644 index 00000000000..d21675537ea Binary files /dev/null and b/docs/images/LogoGithub.png differ diff --git a/docs/images/LogoTelegram.png b/docs/images/LogoTelegram.png new file mode 100644 index 00000000000..7c41a50109e Binary files /dev/null and b/docs/images/LogoTelegram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..0a6a5e2cf67 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/QuickStart.png b/docs/images/QuickStart.png new file mode 100644 index 00000000000..31bcb5581ad Binary files /dev/null and b/docs/images/QuickStart.png differ diff --git a/docs/images/QuickTutorialComponents.png b/docs/images/QuickTutorialComponents.png new file mode 100644 index 00000000000..6ba9fae068a Binary files /dev/null and b/docs/images/QuickTutorialComponents.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..d78804fc6a2 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StudentWithEmptyTelegramAndGitHub.png b/docs/images/StudentWithEmptyTelegramAndGitHub.png new file mode 100644 index 00000000000..15eeb65e7a1 Binary files /dev/null and b/docs/images/StudentWithEmptyTelegramAndGitHub.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..0a1b664a6a8 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 785e04dbab4..34fe1798981 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/hqhqhq1.png b/docs/images/hqhqhq1.png new file mode 100644 index 00000000000..4dd12b37cd6 Binary files /dev/null and b/docs/images/hqhqhq1.png differ diff --git a/docs/images/jaysmyname.png b/docs/images/jaysmyname.png new file mode 100644 index 00000000000..b42e24ae577 Binary files /dev/null and b/docs/images/jaysmyname.png differ diff --git a/docs/images/lowjiahao99.png b/docs/images/lowjiahao99.png new file mode 100644 index 00000000000..59d9d1d57d9 Binary files /dev/null and b/docs/images/lowjiahao99.png differ diff --git a/docs/images/vanessaxuuan.png b/docs/images/vanessaxuuan.png new file mode 100644 index 00000000000..6735f2f8844 Binary files /dev/null and b/docs/images/vanessaxuuan.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..a93359767ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,24 @@ --- layout: page -title: AddressBook Level-3 +title: Teaching Assistant Contact Helper (TACH) --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/AY2122S2-CS2103T-W15-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103T-W15-3/tp/actions) +[![codecov](https://codecov.io/gh/AY2122S2-CS2103T-W15-3/tp/branch/master/graph/badge.svg?token=UXIYT9OQUD)](https://codecov.io/gh/AY2122S2-CS2103T-W15-3/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +The Teaching Assistant Contact Helper (TACH) is a desktop app that helps Computer Science (CS) Teaching Assistants (TAs) +from National University of Singapore (NUS), especially those that tutor +multiple tutorial groups, by **managing their students in an organised manner**. +The sorting feature allows TAs to **view, categorize and get information** of all their students in a glance. +It is optimized for Command Line Interface (CLI) users so that frequent tasks can be done faster by +typing in commands. While it has a GUI (Graphical User Interface), most of the user interactions happen using the CLI. -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using TACH, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TACH, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5), [Markdown-javafx-renderer](https://github.com/JPro-one/markdown-javafx-renderer) diff --git a/docs/team/hqhqhq1.md b/docs/team/hqhqhq1.md new file mode 100644 index 00000000000..0f45be69dc7 --- /dev/null +++ b/docs/team/hqhqhq1.md @@ -0,0 +1,44 @@ +--- +layout: page +title: Huang Qing's Project Portfolio Page +--- + +## Project: Teaching Assistant Contact Helper (TACH) + +TACH helps CS Teaching Assistants tutoring multiple modules & classes by keeping +track of their students and monitoring their progress on their tutorials. +It is optimized for CLI users so that frequent tasks can be done faster by typing +in commands. + +The following sections below summarise my contributions to this project. + +* **Code contributed**: **[RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=hqhqhq1&breakdown=true)** +* **New Feature**: Added find tutorial group feature + * What it does: allows the user to view all students in a particular tutorial group + * Justification: this feature improves the product significantly because users will need to sort students by tutorial + group to have a clear overview of a particular tutorial group, and it also allows users to manage their students by tutorial + groups +* **Project management**: + * set up, keep track and assigning of issues + * update milestones with respective issues and set deadline +* **Enhancements to existing features**: + * Changed add feature to add student feature to suit our product purpose (Pull request [#58](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/58)) + * Added automatically sort student by alphabetical order feature (Pull request [#67](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/67)) + * Updated ui for help window (Pull request [#162](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/162)) + * Wrote additional tests for existing features (Pull request [#58](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/58)) + * Enhanced `edit`, `addtg`, `deletetg` features to keep at current list instead of go back to main list (Pull request [#167](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/167)) +* **Documentation**: + * *User Guide*: + * Added documentation for the features `add` and `findtg` + * Updated command summary + * *Developer Guide*: + * Added use cases and user stories (Pull request [#30](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/30)) + * Added and updated the following sections: Introduction, Acknowledgement and Navigation + * Added implementation details of the `findtg` feature. [#67](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/67)) +* **Contributions to team-based tasks**: + * PRs reviewed (with non-trivial review comments): [#56](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/56), + [#72](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/72), [#83](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/83). + +* **Tools:** + * Integrated a third party library ([markdown-javafx-renderer](https://github.com/JPro-one/markdown-javafx-renderer)) to the project +(Pull request [#162](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/162)) diff --git a/docs/team/jaysmyname.md b/docs/team/jaysmyname.md new file mode 100644 index 00000000000..5cf97bf77ca --- /dev/null +++ b/docs/team/jaysmyname.md @@ -0,0 +1,35 @@ +--- +layout: page +title: Lim Jan Jay's Project Portfolio Page +--- + +## Project: Teaching Assistant Contact Helper (TACH) + +TACH helps CS Teaching Assistants tutoring multiple modules & classes by keeping +track of their students and monitoring their progress on their tutorials. +It is optimized for CLI users so that frequent tasks can be done faster by typing +in commands. + +The following sections below summarise my contributions to this project. + +* **Code contributed**: **[RepoSense Link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=jaysmyname&breakdown=true)** +* **New Feature**: Added the ability to delete tutorial groups of specific students. + * What it does: Allows the user to delete tutorial groups one at a time for a specified student. + * Justification: This feature improves the product significantly because it allows a user to undo an erroneous tutorial group, or delete irrelevant tutorial groups once a semester ends. +* **Project management**: + * Managed releases `v1.2` - `v1.4` (4 releases) on GitHub + * Set deadlines for milestones and made sure they were met. + * Set up the [team repo](https://github.com/AY2122S2-CS2103T-W15-3) +* **Enhancements to existing features**: + * Updated the GUI to fit our product (Pull request [#68](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/68)) + * Improved email and name regexes to be more useful (Pull requests [#150](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/150), [#153](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/153)) +* **Documentation**: + * *User Guide*: + * Added the following sections: Introduction, How to use the guide, Quick Start and Quick Tutorial [#89](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/89) + * Added documentation for the features `addtg` and `deletetg` [#57](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/57) + * Added the Input Requirements section [#57](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/57) + * *Developer Guide*: + * Added implementation details of the `deletetg` feature. + * Added Non-Functional Requirements. +* **Contributions to team-based tasks**: + * PRs reviewed (with non-trivial review comments): [#34](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/34), [#75](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/75), [#77](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/77), [#91](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/91). diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/lowjiahao99.md b/docs/team/lowjiahao99.md new file mode 100644 index 00000000000..73ee637df49 --- /dev/null +++ b/docs/team/lowjiahao99.md @@ -0,0 +1,37 @@ +--- +layout: page +title: Low Jia Hao's Project Portfolio Page +--- + +## Project: Teaching Assistant Contact Helper (TACH) + +TACH helps CS Teaching Assistants tutoring multiple modules & classes by keeping +track of their students and monitoring their progress on their tutorials. +It is optimized for CLI users so that frequent tasks can be done faster by typing +in commands. + +The following sections below summarise my contributions to this project. + +* **New Feature**: Added GitHub, Telegram and Tutorial Group feature. + * What it does: allows users to store GitHub, Telegram and Tutorial Groups as data. + * Justification: This features allows the application to be more specialised to our target users (CS TAs). + * Highlights: GitHub and Telegram are modified to be able to be empty. Difficulty faced in storing data that can be empty because the base project does not allow any empty fields. +* **New Feature**: Added deletetgall feature. + * What it does: Deletes all the provided tutorial group from all students in the list. + * Justification: This feature allows TAs to remove his contact list if he was reassigned to another tutorial group. Can be used at end of semester as well. + +* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=lowjiahao99&breakdown=true) +* **Enhancements to existing features**: + * Modify edit command to be able to accept empty GitHub and Telegram. ([#66](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/66)) +* **Documentation**: + * *User Guide*: + * Added documentation for the feature `add`, `edit` and `deletetgall`. ([#34](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/34), [#66](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/66), [#72](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/72)) + * *Developer Guide*: + * Modify UML diagrams to fit the program. ([#71](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/71)) + * Added implementation details of GitHub and Telegram features. ([#75](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/75)) + * Added value proposition and user stories. ([#28](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/28), [#40](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/40)) +* **Contributions to team-based tasks**: + * Ensuring issues are closed with the PRs attached. + * Updating user stories and value propositions in developer guide. ([#28](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/28), [#40](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/40)) +* **Review/mentoring contributions**: + * PRs reviewed (with non-trivial review comments) : [#36](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/36), [#57](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/57), [#58](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/58), [#60](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/60) diff --git a/docs/team/vanessaxuuan.md b/docs/team/vanessaxuuan.md new file mode 100644 index 00000000000..66d93cc71f5 --- /dev/null +++ b/docs/team/vanessaxuuan.md @@ -0,0 +1,42 @@ +--- +layout: page +title: Khor Vanessa's Project Portfolio Page +--- + +### Project: Teaching Assistant Contact Helper (TACH) + +TACH helps CS Teaching Assistants tutoring multiple modules & classes by keeping track of their students and monitoring their progress on their tutorials. It is optimized for CLI users so that frequent tasks can be done faster by typing in commands. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=vanessaxuuan&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) +* **New Feature**: added `addtg` feature [(#60)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/60) + - What it does: allows users to add tutorial group(s) to an existing student. + - Rationale: this provides greater flexibility for the users as they will be able to add tutorial groups that they have missed out on when the student was being created initially, instead of recreating the student. +* **Enhancements to existing features**: + - Extended `find` feature to allow partial word/phrase matching. [(#172)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/172) + - e.g. `find ja` returns `javier` and `jan`. + - Changed `find` feature to search for students whose names match ALL keywords given instead of ANY keywords. [(#77)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/77) + - e.g. `find Alex Ko` returns `Alex ko` and `Alex koh` but not `Alex` or `Alexius ko`. + - Enhanced `findtg` feature to remove extra whitespace(s). [(#169)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/169) + - e.g. Both `findtg CS2101G08` and `findtg CS2101G08` returns the tutorial group `CS2101 G08`. + - Cosmetic updates of GUI: text wrapping and padding. ([#79](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/79), [#171](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/171)) +* **Contributions to team-based tasks**: + - Set up demo for v1.2. + - PR reviews (with non-trivial comments). [(#34](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/34), [#57](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/57), [#154](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/154)) +* **Project management**: + - Created our discussion group on Discord. + - Note takings during project meetings. + - Managed submissions for User Guide drafts and (CS2101) peer review forms. +* **Documentation**: + * *User Guide* + * Updated the **Commands** section. [(#91)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/91/files) + * Added documentation for `addtg`. + * Added diagrams for `list`, `add`, `deletetgall`, `findtg` and `edit`. + * Added Glossary. + * Updated FAQ. + * Added Quick Start instructions for MacOs users + * *Developer Guide* + * Added Target User Profile. + * Added Glossary. + * Added documentation and UML diagrams for the feature `addtg`. [(#76)](https://github.com/AY2122S2-CS2103T-W15-3/tp/pull/76) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..42f7d860aa2 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, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing TACH ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -79,14 +79,14 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample TACH"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty TACH"); initialData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TACH"); initialData = new AddressBook(); } @@ -151,7 +151,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TACH"); initializedPrefs = new UserPrefs(); } @@ -167,13 +167,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TACH " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping TACH ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..93bc791abcf 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,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_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid! " + + "\n%1$s"; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%1$d students listed!"; } diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..1b902a058cd 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -9,6 +9,9 @@ * convert it back to an int if the index will not be passed to a different component again. */ public class Index { + public static final String MESSAGE_CONSTRAINT = "Index is not a non-zero unsigned integer that is less than or" + + " equal to 2,147,483,647."; + private int zeroBasedIndex; /** diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..2963ff6aee7 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -5,7 +5,7 @@ import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; +import java.math.BigInteger; /** * Helper functions for handling strings. @@ -13,29 +13,44 @@ 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 keys}. + * Ignores case, but white space in {@code keys} must match {@code sentence}'s. *
examples:
-     *       containsWordIgnoreCase("ABc def", "abc") == true
-     *       containsWordIgnoreCase("ABc def", "DEF") == true
-     *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+     *       containsPartialSentenceIgnoreCase("ABc def", "abc") == true
+     *       containsPartialSentenceIgnoreCase("ABc def", "abc d") == true
+     *       containsPartialSentenceIgnoreCase("ABc def", "abcd") == false // whitespace does not tally
+     *       containsPartialSentenceIgnoreCase("ABc def", "abc defg") == false // not a substring
      *       
* @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param keys cannot be null, cannot be empty */ - public static boolean containsWordIgnoreCase(String sentence, String word) { + public static boolean containsPartialSentenceIgnoreCase(String sentence, String keys) { requireNonNull(sentence); - requireNonNull(word); + requireNonNull(keys); - String preppedWord = word.trim(); - checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); - checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + checkArgument(!keys.isBlank(), "Keys cannot be empty"); + String capitalizedName = sentence.toUpperCase().trim(); + String capitalizedKeys = keys.toUpperCase().trim(); + return capitalizedName.contains(capitalizedKeys); + } - String preppedSentence = sentence; - String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + /** + * Returns true if the {@code sentence1} contains the {@code sentence2}. + * Ignores case, but a full sentence match is required. + *
examples:
+     *       containsWordIgnoreCase("ABc def", "abc def") == true
+     *       containsWordIgnoreCase("ABc def", "abc DEF") == true
+     *       containsWordIgnoreCase("ABc def", "ABc") == false //not a full sentence match
+     *       
+ * @param sentence1 cannot be null + * @param sentence2 cannot be null, cannot be empty + */ + public static boolean containsFullSentenceIgnoreCase(String sentence1, String sentence2) { + requireNonNull(sentence1); + requireNonNull(sentence2); - return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + checkArgument(!sentence2.isEmpty(), "sentence2 parameter cannot be empty"); + return sentence1.equalsIgnoreCase(sentence2); } /** @@ -49,16 +64,21 @@ public static String getDetails(Throwable t) { } /** - * Returns true if {@code s} represents a non-zero unsigned integer + * Returns true if {@code s} represents a nonZero unsigned integer that is less than + * or equal to {@code Integer.MAX_VALUE} * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) * @throws NullPointerException if {@code s} is null. */ - public static boolean isNonZeroUnsignedInteger(String s) { - requireNonNull(s); - + public static boolean isNonZeroSignedIntegerLessThanOrEqualToIntegerLimit(String s) { try { + BigInteger parsedInteger = new BigInteger(s); + Integer maxInt = Integer.MAX_VALUE; + String maxIntString = maxInt.toString(); + if (parsedInteger.compareTo(new BigInteger(maxIntString)) == 1) { //check for overflow + return false; + } int value = Integer.parseInt(s); return value > 0 && !s.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String) } catch (NumberFormatException nfe) { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..6696e55727a 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * API of the Logic component @@ -30,8 +30,8 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of students */ + ObservableList getFilteredStudentList(); /** * Returns the user prefs' address book file path. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..d80ff2e1ded 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,7 +14,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; import seedu.address.storage.Storage; /** @@ -60,8 +60,8 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -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_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - 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"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddStudentCommand.java b/src/main/java/seedu/address/logic/commands/AddStudentCommand.java new file mode 100644 index 00000000000..2d1cf906d26 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddStudentCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUB; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; + +/** + * Adds a student to the address book. + */ +public class AddStudentCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_EMAIL + "EMAIL " + + "[" + PREFIX_TELEGRAM + "TELEGRAM] " + + "[" + PREFIX_GITHUB + "GITHUB] " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL_GROUP...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_EMAIL + "e0123456@u.nus.edu " + + PREFIX_TELEGRAM + "johndoe201 " + + PREFIX_GITHUB + "john-doe " + + PREFIX_TUTORIAL_GROUP + "CS2103T W15-3 " + + PREFIX_TUTORIAL_GROUP + "CS2101 G08"; + + public static final String MESSAGE_SUCCESS = "New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the address book"; + + private final Student toAdd; + + /** + * Creates an AddStudentCommand to add the specified {@code Student} + */ + public AddStudentCommand(Student student) { + requireNonNull(student); + toAdd = student; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasStudent(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.addStudent(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddStudentCommand // instanceof handles nulls + && toAdd.equals(((AddStudentCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTutorialGroupCommand.java b/src/main/java/seedu/address/logic/commands/AddTutorialGroupCommand.java new file mode 100644 index 00000000000..a0d58a1f97d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTutorialGroupCommand.java @@ -0,0 +1,191 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Adds tutorial group to a student identified using it's displayed index from the address book + */ +public class AddTutorialGroupCommand extends Command { + + public static final String COMMAND_WORD = "addtg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds tutorial groups to the student identified " + + "by the index number used in the displayed student list. " + + "Adding of tutorial groups is cumulative.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL_GROUP...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_TUTORIAL_GROUP + "CS2103T W15-3 " + + PREFIX_TUTORIAL_GROUP + "CS2101 G08"; + + public static final String MESSAGE_ADD_TUTORIAL_GROUP_SUCCESS = "Added tutorial group: %1$s"; + public static final String MESSAGE_NOT_ADDED = "At least one tutorial group to add must be provided."; + public static final String MESSAGE_DUPLICATE_TUTORIAL_GROUP = "This tutorial group already exists."; + public static final String MESSAGE_INDEX_OUT_OF_RANGE = String.format( + Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, "Index is larger than the number of " + + "students in the viewed list."); + + private final Index index; + private final AddTutorialGroupDescriptor addTutorialGroupDescriptor; + + /** + * @param index of the student in the filtered student list to edit + * @param details to edit the student with + */ + public AddTutorialGroupCommand(Index index, AddTutorialGroupDescriptor details) { + requireNonNull(index); + requireNonNull(details); + + this.index = index; + this.addTutorialGroupDescriptor = new AddTutorialGroupDescriptor(details); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INDEX_OUT_OF_RANGE); + } + Student studentToEdit = lastShownList.get(index.getZeroBased()); + + // identify duplicate tutorial groups + if (studentToEdit.tutorialGroupExists(addTutorialGroupDescriptor.tutorialGroups)) { + throw new CommandException(MESSAGE_DUPLICATE_TUTORIAL_GROUP); + } + + Student updatedStudent = createNewStudent(studentToEdit, addTutorialGroupDescriptor); + model.setStudent(studentToEdit, updatedStudent); + model.getFilteredStudentList(); + return new CommandResult(String.format(MESSAGE_ADD_TUTORIAL_GROUP_SUCCESS, + addTutorialGroupDescriptor.tutorialGroups)); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code ddTutorialGroupDescriptor}. + */ + private static Student createNewStudent(Student studentToEdit, AddTutorialGroupDescriptor tgDescriptor) { + assert studentToEdit != null; + // Defensive copy of tgDescriptor + AddTutorialGroupDescriptor tgDescriptorCopy = new AddTutorialGroupDescriptor(tgDescriptor); + + Name currName = studentToEdit.getName(); + Email currEmail = studentToEdit.getEmail(); + + Telegram currTelegram = studentToEdit.getTelegram(); + GitHub currGitHub = studentToEdit.getGitHub(); + + tgDescriptorCopy.addTutorialGroups(studentToEdit.getTutorialGroups()); + Set updatedTutorialGroups = tgDescriptorCopy.getTutorialGroups().get(); + + return new Student(currName, currTelegram, currEmail, currGitHub, updatedTutorialGroups); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddTutorialGroupCommand)) { + return false; + } + + // state check + AddTutorialGroupCommand e = (AddTutorialGroupCommand) other; + return index.equals(e.index) + && addTutorialGroupDescriptor.equals(e.addTutorialGroupDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class AddTutorialGroupDescriptor { + private Set tutorialGroups; + + public AddTutorialGroupDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tutorialGroups} is used internally. + */ + public AddTutorialGroupDescriptor(AddTutorialGroupDescriptor toCopy) { + setTutorialGroups(toCopy.tutorialGroups); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(tutorialGroups); + } + + /** + * Sets {@code tutorialGroups} to this object's {@code tutorialGroups}. + * A defensive copy of {@code tutorialGroups} is used internally. + */ + public void setTutorialGroups(Set tutorialGroups) { + this.tutorialGroups = (tutorialGroups != null) ? new HashSet<>(tutorialGroups) : null; + } + + /** + * Returns an unmodifiable tutorial group set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tutorialGroups} is null. + */ + public Optional> getTutorialGroups() { + return (tutorialGroups != null) + ? Optional.of(Collections.unmodifiableSet(tutorialGroups)) : Optional.empty(); + } + + /** + * Adds additional tutorial groups to the current AddTutorialGroupDescriptor + * + * @param tg tutorial group(s) to be added + */ + public void addTutorialGroups(Set tg) { + tutorialGroups.addAll(tg); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddTutorialGroupDescriptor)) { + return false; + } + + // state check + AddTutorialGroupDescriptor a = (AddTutorialGroupDescriptor) other; + return getTutorialGroups().equals(a.getTutorialGroups()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..29c7795a84c 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -8,21 +8,24 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a student identified using it's displayed index from the address book. */ 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 student 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_STUDENT_SUCCESS = "Deleted Student: %1$s"; + public static final String MESSAGE_INDEX_OUT_OF_RANGE = String.format( + Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, "Index is larger than the number of " + + "students in the viewed list."); private final Index targetIndex; @@ -33,15 +36,15 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(MESSAGE_INDEX_OUT_OF_RANGE); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Student studentToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteStudent(studentToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, studentToDelete)); } @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupCommand.java new file mode 100644 index 00000000000..0d5dbcb1725 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupCommand.java @@ -0,0 +1,151 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Deletes the specified tutorial group from a student using their displayed index + */ +public class DeleteTutorialGroupCommand extends Command { + + public static final String COMMAND_WORD = "deletetg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the specified tutorial group " + + "from a student identified by the index number used in the displayed student list.\n" + + "Only one tutorial group can be deleted at a time.\n" + + "The tutorial group must be written EXACTLY, but is case-insensitive.\n" + + "The tutorial group cannot be deleted if it is the ONLY tutorial group a student has.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL_GROUP\n" + + "Example: " + COMMAND_WORD + " 2 " + + PREFIX_TUTORIAL_GROUP + "Cs2101 g08"; + + public static final String MESSAGE_DELETE_TUTORIAL_GROUP_SUCCESS = "Deleted Tutorial Group: %1$s"; + public static final String MESSAGE_NOT_DELETED = "Exactly one tutorial group must be provided."; + public static final String MESSAGE_NO_SUCH_TUTORIAL_GROUP = "The student at this index does not " + + "have this tutorial group."; + public static final String MESSAGE_CANNOT_DELETE_ONLY_TUTORIAL_GROUP = "This tutorial group cannot be deleted " + + "because the student at this index only has this tutorial group."; + public static final String MESSAGE_INDEX_OUT_OF_RANGE = String.format( + Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, "Index is larger than the number of " + + "students in the viewed list."); + + private final Index index; + private final TutorialGroup tutorialGroupToDelete; + + /** + * DeleteTutorialGroupCommand constructor. + * + * @param index of the student in the filtered student list to edit + * @param tutorialGroupToDelete {@code String} of tutorial group to delete from student + */ + public DeleteTutorialGroupCommand(Index index, String tutorialGroupToDelete) { + requireNonNull(index); + requireNonNull(tutorialGroupToDelete); + + this.index = index; + this.tutorialGroupToDelete = new TutorialGroup(tutorialGroupToDelete); + } + + /** + * Another DeleteTutorialGroupCommand constructor. + * + * @param index of the student in the filtered student list to edit + * @param tutorialGroupToDelete {@code TutorialGroup} to delete from student + */ + public DeleteTutorialGroupCommand(Index index, TutorialGroup tutorialGroupToDelete) { + requireNonNull(index); + requireNonNull(tutorialGroupToDelete); + + this.index = index; + this.tutorialGroupToDelete = tutorialGroupToDelete; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_INDEX_OUT_OF_RANGE); + } + Student studentToEdit = lastShownList.get(index.getZeroBased()); + + // Identify if tutorial group does not exist + if (!studentToEdit.tutorialGroupExists(tutorialGroupToDelete)) { + throw new CommandException(MESSAGE_NO_SUCH_TUTORIAL_GROUP); + } + + Student updatedStudent = createNewStudent(studentToEdit, tutorialGroupToDelete); + model.setStudent(studentToEdit, updatedStudent); + model.getFilteredStudentList(); + return new CommandResult(String.format(MESSAGE_DELETE_TUTORIAL_GROUP_SUCCESS, updatedStudent)); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code deleteTutorialGroupDescriptor}. + */ + private static Student createNewStudent(Student studentToEdit, TutorialGroup tutorialGroupToDelete) + throws CommandException { + assert studentToEdit != null; + + Name currName = studentToEdit.getName(); + Email currEmail = studentToEdit.getEmail(); + Telegram currTelegram = studentToEdit.getTelegram(); + GitHub currGitHub = studentToEdit.getGitHub(); + + Set currTutorialGroups = studentToEdit.getTutorialGroups(); + + assert studentToEdit.tutorialGroupExists(tutorialGroupToDelete); + + // Identify if tutorial group to delete is the only tutorial group + if (currTutorialGroups.size() == 1) { + throw new CommandException(MESSAGE_CANNOT_DELETE_ONLY_TUTORIAL_GROUP); + } + + Set updatedTutorialGroups = new HashSet<>(); + for (TutorialGroup tg : currTutorialGroups) { + if (tg.equals(tutorialGroupToDelete)) { + continue; + } + updatedTutorialGroups.add(tg); + } + + return new Student(currName, currTelegram, currEmail, currGitHub, updatedTutorialGroups); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteTutorialGroupCommand)) { + return false; + } + + // state check + DeleteTutorialGroupCommand d = (DeleteTutorialGroupCommand) other; + return index.equals(d.index) + && tutorialGroupToDelete.equals(d.tutorialGroupToDelete); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupsFromStudentsCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupsFromStudentsCommand.java new file mode 100644 index 00000000000..b7bd788f80c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTutorialGroupsFromStudentsCommand.java @@ -0,0 +1,107 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Deletes tutorial groups given from all students in TACH. If student does not have a tutorial group after deleting + * the student will be deleted. + */ +public class DeleteTutorialGroupsFromStudentsCommand extends Command { + + public static final String COMMAND_WORD = "deletetgall"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the tutorial group identified by the prefix from all students.\n" + + "Students without a tutorial group after the deletion will also be deleted.\n" + + "Parameters: " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL_GROUPS...\n" + + "Example: " + COMMAND_WORD + PREFIX_TUTORIAL_GROUP + "CS2103-W15-3" + " " + + PREFIX_TUTORIAL_GROUP + "CS2101 G08"; + + public static final String MESSAGE_TUTORIAL_GROUP_DELETE_SUCCESS = "Deleted tutorial group(s): %1$s"; + + private final Set tutorialGroupsToDelete; + + public DeleteTutorialGroupsFromStudentsCommand(Set tutorialGroupsToDelete) { + this.tutorialGroupsToDelete = tutorialGroupsToDelete; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List studentList = model.getSortedStudentList(); + List studentsToRemoveTutorialGroup = new ArrayList<>(); + for (Student student : studentList) { + studentsToRemoveTutorialGroup.add(student); + } + + for (Student studentToRemoveTutorialGroup : studentsToRemoveTutorialGroup) { + Set updatedTutorialGroupForStudent = removeTutorialGroups( + studentToRemoveTutorialGroup.getTutorialGroups(), tutorialGroupsToDelete); + if (updatedTutorialGroupForStudent.size() == 0) { + model.deleteStudent(studentToRemoveTutorialGroup); + } else { + Student updatedStudent = createNewStudent(studentToRemoveTutorialGroup, updatedTutorialGroupForStudent); + model.setStudent(studentToRemoveTutorialGroup, updatedStudent); + } + } + + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(String.format(MESSAGE_TUTORIAL_GROUP_DELETE_SUCCESS, + tutorialGroupsToDelete.toString())); + } + + private static Set removeTutorialGroups(Set targetTutorialGroups, + Set tutorialGroupsToRemove) { + Set updatedTutorialGroup = new HashSet<>(); + updatedTutorialGroup.addAll(targetTutorialGroups); + for (TutorialGroup tgtr : tutorialGroupsToRemove) { + for (TutorialGroup ttg : targetTutorialGroups) { + if (ttg.equals(tgtr)) { + updatedTutorialGroup.remove(ttg); + } + } + } + return updatedTutorialGroup; + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code Set}. + */ + private static Student createNewStudent(Student studentToEdit, Set newTutorialGroup) { + assert studentToEdit != null; + + Name currName = studentToEdit.getName(); + Email currEmail = studentToEdit.getEmail(); + + Telegram currTelegram = studentToEdit.getTelegram(); + GitHub currGitHub = studentToEdit.getGitHub(); + + return new Student(currName, currTelegram, currEmail, currGitHub, newTutorialGroup); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTutorialGroupsFromStudentsCommand // instanceof handles nulls + && tutorialGroupsToDelete.equals(((DeleteTutorialGroupsFromStudentsCommand) other) + .tutorialGroupsToDelete)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..c7406d1294a 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,105 +1,126 @@ 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_GITHUB; 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.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; +import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -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.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing student in the address book. */ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the student identified " + + "by the index number used in the displayed student list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_TELEGRAM + "TELEGRAM] " + + "[" + PREFIX_GITHUB + "GITHUB] " + + "[" + PREFIX_TUTORIAL_GROUP + "TUTORIAL_GROUP]...\n" + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_TELEGRAM + "johndoe " + + PREFIX_EMAIL + "e0123456@u.nus.edu"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_STUDENT_SUCCESS = "Edited Student: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the address book."; + public static final String MESSAGE_INDEX_OUT_OF_RANGE = String.format( + Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, "Index is larger than the number of " + + "students in the viewed list."); private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditStudentDescriptor editStudentDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index of the student in the filtered student list to edit + * @param editStudentDescriptor details to edit the student with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditStudentDescriptor editStudentDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); + requireNonNull(editStudentDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(MESSAGE_INDEX_OUT_OF_RANGE); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Student studentToEdit = lastShownList.get(index.getZeroBased()); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!studentToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + model.setStudent(studentToEdit, editedStudent); + + //keep the current list of filtered student and edited student + if (!model.getFilteredStudentList().equals(model.getSortedStudentList())) { + FilteredList filteredStudents = (FilteredList) model.getFilteredStudentList(); + + @SuppressWarnings("unchecked") + //test.getPredicate() must be a Predicate type + Predicate predicateForFilteredStudents = (Predicate) filteredStudents.getPredicate(); + + Predicate predicateToObtainEditedStudent = (Student s) -> s.equals(editedStudent); + + model.updateFilteredStudentList(predicateForFilteredStudents.or(predicateToObtainEditedStudent)); + } else { + //the list is not filtered, show all students + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + } + return new CommandResult(String.format(MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent)); } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; - 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()); + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Telegram updatedTelegram = editStudentDescriptor.getTelegram().orElse(studentToEdit.getTelegram()); + GitHub updatedGitHub = editStudentDescriptor.getGitHub().orElse(studentToEdit.getGitHub()); + Set updatedTutorialGroups = editStudentDescriptor.getTutorialGroups() + .orElse(studentToEdit.getTutorialGroups()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Student(updatedName, updatedTelegram, updatedEmail, updatedGitHub, updatedTutorialGroups); } @Override @@ -117,39 +138,39 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); + && editStudentDescriptor.equals(e.editStudentDescriptor); } /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. */ - public static class EditPersonDescriptor { + public static class EditStudentDescriptor { private Name name; - private Phone phone; + private Telegram telegram; private Email email; - private Address address; - private Set tags; + private GitHub gitHub; + private Set tutorialGroups; - public EditPersonDescriptor() {} + public EditStudentDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code tutorialGroups} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditStudentDescriptor(EditStudentDescriptor toCopy) { setName(toCopy.name); - setPhone(toCopy.phone); + setTelegram(toCopy.telegram); setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); + setGitHub(toCopy.gitHub); + setTutorialGroups(toCopy.tutorialGroups); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, telegram, email, gitHub, tutorialGroups); } public void setName(Name name) { @@ -160,12 +181,12 @@ public Optional getName() { return Optional.ofNullable(name); } - public void setPhone(Phone phone) { - this.phone = phone; + public void setTelegram(Telegram telegram) { + this.telegram = telegram; } - public Optional getPhone() { - return Optional.ofNullable(phone); + public Optional getTelegram() { + return Optional.ofNullable(telegram); } public void setEmail(Email email) { @@ -176,29 +197,30 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setGitHub(GitHub gitHub) { + this.gitHub = gitHub; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getGitHub() { + return Optional.ofNullable(gitHub); } /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Sets {@code tutorialGroups} to this object's {@code tutorialGroups}. + * A defensive copy of {@code tutorialGroups} is used internally. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setTutorialGroups(Set tutorialGroups) { + this.tutorialGroups = (tutorialGroups != null) ? new HashSet<>(tutorialGroups) : null; } /** * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns {@code Optional#empty()} if {@code tutorialGroups} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional> getTutorialGroups() { + return (tutorialGroups != null) + ? Optional.of(Collections.unmodifiableSet(tutorialGroups)) : Optional.empty(); } @Override @@ -209,18 +231,18 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditStudentDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditStudentDescriptor e = (EditStudentDescriptor) other; return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) + && getTelegram().equals(e.getTelegram()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getGitHub().equals(e.getGitHub()) + && getTutorialGroups().equals(e.getTutorialGroups()); } } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..a5544180806 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -4,20 +4,20 @@ import seedu.address.commons.core.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.student.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all students in address book whose name contains all the argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students whose names contain all of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Example: " + COMMAND_WORD + " John Smith"; private final NameContainsKeywordsPredicate predicate; @@ -28,9 +28,9 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredStudentList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, model.getFilteredStudentList().size())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/FindTutorialGroupCommand.java b/src/main/java/seedu/address/logic/commands/FindTutorialGroupCommand.java new file mode 100644 index 00000000000..b986d24f373 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindTutorialGroupCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.tutorialgroup.TutorialGroupKeywordsPredicate; + +public class FindTutorialGroupCommand extends Command { + public static final String COMMAND_WORD = "findtg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students in a tutorial group " + + "sorted (alphabetical order) and displays them as a list with index numbers.\n" + + "Parameters: Module code and Tutorial name\n" + + "Example: " + COMMAND_WORD + " CS2103T W15-3"; + + private final TutorialGroupKeywordsPredicate predicate; + + public FindTutorialGroupCommand(TutorialGroupKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredStudentList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, model.getFilteredStudentList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTutorialGroupCommand // instanceof handles nulls + && predicate.equals(((FindTutorialGroupCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..77ab100be7c 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,24 +1,24 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; import seedu.address.model.Model; /** - * Lists all persons in the address book to the user. + * Lists all students in the address book to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all students"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser 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 AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - 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)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * 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/AddStudentCommandParser.java b/src/main/java/seedu/address/logic/parser/AddStudentCommandParser.java new file mode 100644 index 00000000000..8f042573eec --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddStudentCommandParser.java @@ -0,0 +1,67 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUB; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddStudentCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Parses input arguments and creates a new AddStudentCommand object + */ +public class AddStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddStudentCommand + * and returns an AddStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TELEGRAM, PREFIX_EMAIL, + PREFIX_GITHUB, PREFIX_TUTORIAL_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_EMAIL, PREFIX_TUTORIAL_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddStudentCommand.MESSAGE_USAGE)); + } + + String telegramString = argMultimap.getValue(PREFIX_TELEGRAM).isPresent() + ? argMultimap.getValue(PREFIX_TELEGRAM).get() : null; + String gitHubString = argMultimap.getValue(PREFIX_GITHUB).isPresent() + ? argMultimap.getValue(PREFIX_GITHUB).get() : null; + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Telegram telegram = ParserUtil.parseTelegram(telegramString); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + GitHub gitHub = ParserUtil.parseGitHub(gitHubString); + Set tutorialGroupList = ParserUtil.parseTutorialGroups( + argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP)); + + Student student = new Student(name, telegram, email, gitHub, tutorialGroupList); + + return new AddStudentCommand(student); + } + + /** + * 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/AddTutorialGroupParser.java b/src/main/java/seedu/address/logic/parser/AddTutorialGroupParser.java new file mode 100644 index 00000000000..7e6cf7d0866 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTutorialGroupParser.java @@ -0,0 +1,56 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddTutorialGroupCommand; +import seedu.address.logic.commands.AddTutorialGroupCommand.AddTutorialGroupDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Parses input arguments and creates a new AddTutorialGroupCommand object + */ +public class AddTutorialGroupParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddTutorialGroupCommand + * and returns an AddTutorialGroupCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddTutorialGroupCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL_GROUP); + + // Exception thrown if prefix or value missing + if (argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddTutorialGroupCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, pe.getMessage())); + } + + AddTutorialGroupDescriptor addTutorialGroupDescriptor = new AddTutorialGroupDescriptor(); + Set tutorialGroupList = ParserUtil.parseTutorialGroups(argMultimap + .getAllValues(PREFIX_TUTORIAL_GROUP)); + addTutorialGroupDescriptor.setTutorialGroups(tutorialGroupList); + + if (!addTutorialGroupDescriptor.isAnyFieldEdited()) { + throw new ParseException(AddTutorialGroupCommand.MESSAGE_NOT_ADDED); + } + + return new AddTutorialGroupCommand(index, addTutorialGroupDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..d544a5f1a66 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -6,13 +6,17 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddStudentCommand; +import seedu.address.logic.commands.AddTutorialGroupCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteTutorialGroupCommand; +import seedu.address.logic.commands.DeleteTutorialGroupsFromStudentsCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindTutorialGroupCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -44,8 +48,8 @@ public Command parseCommand(String userInput) throws ParseException { final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case AddStudentCommand.COMMAND_WORD: + return new AddStudentCommandParser().parse(arguments); case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); @@ -59,6 +63,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FindTutorialGroupCommand.COMMAND_WORD: + return new FindTutorialGroupParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); @@ -68,6 +75,15 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case AddTutorialGroupCommand.COMMAND_WORD: + return new AddTutorialGroupParser().parse(arguments); + + case DeleteTutorialGroupsFromStudentsCommand.COMMAND_WORD: + return new DeleteTutorialGroupsFromStudentsParser().parse(arguments); + + case DeleteTutorialGroupCommand.COMMAND_WORD: + return new DeleteTutorialGroupParser().parse(arguments); + default: 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..fc1ac418bcc 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,9 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_TELEGRAM = new Prefix("t/"); 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_GITHUB = new Prefix("g/"); + public static final Prefix PREFIX_TUTORIAL_GROUP = new Prefix("tg/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 522b93081cc..e553e7f363f 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,6 +1,6 @@ package seedu.address.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; @@ -22,7 +22,7 @@ public DeleteCommand parse(String args) throws ParseException { return new DeleteCommand(index); } catch (ParseException pe) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + String.format(String.format(MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, pe.getMessage()))); } } diff --git a/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupParser.java b/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupParser.java new file mode 100644 index 00000000000..ab31e4cb597 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteTutorialGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Parses input arguments and creates a new DeleteTutorialGroupCommand object + */ +public class DeleteTutorialGroupParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTutorialGroupCommand + * and returns a DeleteTutorialGroupCommand object for execution. + * + * @throws ParseException if the user input does not conform with the expected format. + */ + public DeleteTutorialGroupCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL_GROUP); + + // Exception thrown if prefix or value missing + if (argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTutorialGroupCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, pe.getMessage())); + } + + // Exception thrown if more than one value + if (argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP).size() > 1) { + throw new ParseException(DeleteTutorialGroupCommand.MESSAGE_NOT_DELETED); + } + + assert argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP).size() == 1; + + TutorialGroup tutorialGroupToDelete = ParserUtil.parseTutorialGroup(argMultimap + .getAllValues(PREFIX_TUTORIAL_GROUP) + .get(0)); + + return new DeleteTutorialGroupCommand(index, tutorialGroupToDelete); + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupsFromStudentsParser.java b/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupsFromStudentsParser.java new file mode 100644 index 00000000000..00062028466 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTutorialGroupsFromStudentsParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.Set; + +import seedu.address.logic.commands.DeleteTutorialGroupsFromStudentsCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Parses input arguments and creates a new DeleteTutorialGroupsFromStudents object + */ +public class DeleteTutorialGroupsFromStudentsParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTutorialGroupsFromStudentsCommand + * and returns a DeleteTutorialGroupsFromStudentsCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTutorialGroupsFromStudentsCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL_GROUP); + + // Exception thrown if prefix or value missing + if (argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTutorialGroupsFromStudentsCommand.MESSAGE_USAGE)); + } + + Set tutorialGroupListToDelete = ParserUtil.parseTutorialGroups(argMultimap + .getAllValues(PREFIX_TUTORIAL_GROUP)); + + return new DeleteTutorialGroupsFromStudentsCommand(tutorialGroupListToDelete); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..f0bdbe032ad 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -1,23 +1,22 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GITHUB; 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_TELEGRAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; import java.util.Collection; -import java.util.Collections; import java.util.Optional; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditCommand.EditStudentDescriptor; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.address.model.tutorialgroup.TutorialGroup; /** * Parses input arguments and creates a new EditCommand object @@ -32,51 +31,64 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TELEGRAM, + PREFIX_EMAIL, PREFIX_GITHUB, PREFIX_TUTORIAL_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); + throw new ParseException(String.format(MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX, pe.getMessage())); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditStudentDescriptor editStudentDescriptor = new EditCommand.EditStudentDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + editStudentDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + if (argMultimap.getValue(PREFIX_TELEGRAM).isPresent()) { + if ((argMultimap.getValue(PREFIX_TELEGRAM).get().equals(""))) { + editStudentDescriptor.setTelegram(ParserUtil.parseTelegram(null)); + } else { + editStudentDescriptor.setTelegram(ParserUtil.parseTelegram( + argMultimap.getValue(PREFIX_TELEGRAM).get())); + } } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editStudentDescriptor.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_GITHUB).isPresent()) { + if ((argMultimap.getValue(PREFIX_GITHUB).get().equals(""))) { + editStudentDescriptor.setGitHub(ParserUtil.parseGitHub(null)); + } else { + editStudentDescriptor.setGitHub(ParserUtil.parseGitHub(argMultimap.getValue(PREFIX_GITHUB).get())); + } } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseTutorialGroupsForEdit(argMultimap.getAllValues(PREFIX_TUTORIAL_GROUP)) + .ifPresent(editStudentDescriptor::setTutorialGroups); - if (!editPersonDescriptor.isAnyFieldEdited()) { + if (!editStudentDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(index, editStudentDescriptor); } /** - * 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 tutorialGroups} into a {@code Set} given that + * {@code tutorialGroups} is non-empty. + * + * @throws ParseException if {@code tutorialGroups} contain only one element which is an empty string */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; + private Optional> parseTutorialGroupsForEdit( + Collection tutorialGroups) throws ParseException { + assert tutorialGroups != null; - if (tags.isEmpty()) { + if (tutorialGroups.isEmpty()) { return Optional.empty(); } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + + return Optional.of(ParserUtil.parseTutorialGroups(tutorialGroups)); } } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..9e0833aca08 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.student.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object diff --git a/src/main/java/seedu/address/logic/parser/FindTutorialGroupParser.java b/src/main/java/seedu/address/logic/parser/FindTutorialGroupParser.java new file mode 100644 index 00000000000..872357b201b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindTutorialGroupParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.FindTutorialGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorialgroup.TutorialGroupKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindTutorialGroupCommand object + */ +public class FindTutorialGroupParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindTutorialGroupCommand + * and returns a FindTutorialGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTutorialGroupCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTutorialGroupCommand.MESSAGE_USAGE)); + } + + // Check for presence of whitespace + int whitespacePos = trimmedArgs.indexOf(" "); + if (whitespacePos == -1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTutorialGroupCommand.MESSAGE_USAGE)); + } + + // remove extra whitespaces + String moduleCode = trimmedArgs.substring(0, whitespacePos).trim(); + String trimmedGroup = trimmedArgs.substring(whitespacePos).trim(); + String trimmedTutorialGroup = moduleCode + " " + trimmedGroup; + return new FindTutorialGroupCommand(new TutorialGroupKeywordsPredicate(trimmedTutorialGroup)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..619dc5e1342 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,19 +9,17 @@ 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.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. @@ -29,9 +27,10 @@ public class ParserUtil { */ public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); + if (!StringUtil.isNonZeroSignedIntegerLessThanOrEqualToIntegerLimit(trimmedIndex)) { + throw new ParseException(Index.MESSAGE_CONSTRAINT); } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } @@ -51,33 +50,37 @@ public static Name parseName(String name) throws ParseException { } /** - * Parses a {@code String phone} into a {@code Phone}. + * Parses a {@code String telegram} into a {@code Telegram}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code phone} is invalid. + * @throws ParseException if the given {@code telegram} is invalid. */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + public static Telegram parseTelegram(String telegram) throws ParseException { + if (telegram == null) { + return new Telegram(null); } - return new Phone(trimmedPhone); + String trimmedTelegram = telegram.trim(); + if (!Telegram.isValidTelegram(trimmedTelegram)) { + throw new ParseException(Telegram.MESSAGE_CONSTRAINTS); + } + return new Telegram(trimmedTelegram); } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String gitHub} into an {@code GitHub}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code gitHub} 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 GitHub parseGitHub(String gitHub) throws ParseException { + if (gitHub == null) { + return new GitHub(null); + } + String trimmedGitHub = gitHub.trim(); + if (!GitHub.isValidGitHub(trimmedGitHub)) { + throw new ParseException(GitHub.MESSAGE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new GitHub(trimmedGitHub); } /** @@ -96,29 +99,29 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String tutorialGroup} into a {@code TutorialGroup}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code tutorialGroup} 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 TutorialGroup parseTutorialGroup(String tutorialGroup) throws ParseException { + requireNonNull(tutorialGroup); + String trimmedTutorialGroup = tutorialGroup.trim(); + if (!TutorialGroup.isValidTutorialGroupName(trimmedTutorialGroup)) { + throw new ParseException(TutorialGroup.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new TutorialGroup(trimmedTutorialGroup.toUpperCase()); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses {@code Collection tutorialGroups} into a {@code Set}. */ - 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 Set parseTutorialGroups(Collection tutorialGroups) throws ParseException { + requireNonNull(tutorialGroups); + final Set tutorialGroupSet = new HashSet<>(); + for (String tutorialGroupName : tutorialGroups) { + tutorialGroupSet.add(parseTutorialGroup(tutorialGroupName)); } - return tagSet; + return tutorialGroupSet; } } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..2691cd5ba10 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -5,8 +5,8 @@ import java.util.List; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.student.Student; +import seedu.address.model.student.UniqueStudentList; /** * Wraps all data at the address-book level @@ -14,7 +14,7 @@ */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueStudentList students; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -24,13 +24,13 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + students = new UniqueStudentList(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates an AddressBook using the Students in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -40,11 +40,11 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setStudents(List students) { + this.students.setStudents(students); } /** @@ -53,68 +53,69 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setStudents(newData.getStudentList()); } - //// person-level operations + //// student-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasStudent(Student student) { + requireNonNull(student); + return students.contains(student); } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a student to the address book. + * The student must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addStudent(Student s) { + students.add(s); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given student {@code target} in the list with {@code editedStudent}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The student identity of {@code editedStudent} must not + * be the same as another existing student in the address book. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); - persons.setPerson(target, editedPerson); + students.setStudent(target, editedStudent); } /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void removeStudent(Student key) { + students.remove(key); } //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + return students.asUnmodifiableObservableList().size() + " students"; // TODO: refine later } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && students.equals(((AddressBook) other).students)); } @Override public int hashCode() { - return persons.hashCode(); + return students.hashCode(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..efd2972b423 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,14 +5,14 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -53,35 +53,40 @@ public interface Model { ReadOnlyAddressBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasStudent(Student student); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given student. + * The student must exist in the address book. */ - void deletePerson(Person target); + void deleteStudent(Student target); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Adds the given student. + * {@code student} must not already exist in the address book. */ - void addPerson(Person person); + void addStudent(Student student); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given student {@code target} with {@code editedStudent}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The student identity of {@code editedStudent} must not + * be the same as another existing student in the address book. */ - void setPerson(Person target, Person editedPerson); + void setStudent(Student target, Student editedStudent); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered student list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredStudentList(Predicate predicate); + + /** Returns an unmodifiable view of the student list in address book in sorted order + */ + ObservableList getSortedStudentList(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..a2250b6cb08 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -9,9 +9,10 @@ import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * Represents the in-memory model of the address book data. @@ -21,7 +22,8 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredStudents; + private final SortedList sortedStudents; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -33,7 +35,8 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + sortedStudents = new SortedList(this.addressBook.getStudentList().sorted()); + filteredStudents = new FilteredList<>(sortedStudents); } public ModelManager() { @@ -88,44 +91,49 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public boolean hasStudent(Student student) { + requireNonNull(student); + return addressBook.hasStudent(student); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void deleteStudent(Student target) { + addressBook.removeStudent(target); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addStudent(Student student) { + addressBook.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); - addressBook.setPerson(target, editedPerson); + addressBook.setStudent(target, editedStudent); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public ObservableList getSortedStudentList() { + return sortedStudents; + } + + //=========== Filtered Student List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Student} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredStudentList() { + return filteredStudents; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredStudentList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredStudents.setPredicate(predicate); } @Override @@ -144,7 +152,7 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredStudents.equals(other.filteredStudents); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..4fac98ec6b0 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * Unmodifiable view of an address book @@ -9,9 +9,9 @@ public interface ReadOnlyAddressBook { /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. + * Returns an unmodifiable view of the students list. + * This list will not contain any duplicate students. */ - ObservableList getPersonList(); + ObservableList getStudentList(); } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 8ff1d83fe89..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,123 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -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.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/student/Email.java similarity index 68% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/student/Email.java index f866e7133de..850facf6207 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/student/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -14,21 +14,15 @@ public class Email { + "and adhere to the following constraints:\n" + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " - + "characters.\n" - + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " - + "separated by periods.\n" - + "The domain name must:\n" - + " - end with a domain label at least 2 characters long\n" - + " - have each domain label start and end with alphanumeric characters\n" - + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; + + "characters. The local-part cannot be longer than 64 characters.\n" + + "2. This is followed by a '@' and then a domain name. The domain name must be one of the following:\n" + + "u.nus.edu nus.edu.sg gmail.com yahoo.com outlook.com hotmail.com"; // alphanumeric and special characters private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE - + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars - private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; + private static final String DOMAIN_REGEX = "(u\\.nus\\.edu|nus\\.edu\\.sg|gmail\\.com" + + "|yahoo\\.com|outlook\\.com|hotmail\\.com)"; public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; public final String value; @@ -48,7 +42,12 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + if (test.matches(VALIDATION_REGEX)) { + String[] localAndDomain = test.split("@"); + return localAndDomain[0].length() <= 64; + } else { + return false; + } } @Override diff --git a/src/main/java/seedu/address/model/student/GitHub.java b/src/main/java/seedu/address/model/student/GitHub.java new file mode 100644 index 00000000000..2dd0698914a --- /dev/null +++ b/src/main/java/seedu/address/model/student/GitHub.java @@ -0,0 +1,69 @@ +package seedu.address.model.student; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's GitHub in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidGitHub(String)} + */ +public class GitHub { + + public static final String MESSAGE_CONSTRAINTS = "Github username must only contain alphanumeric " + + "characters or hyphens.\n Github username cannot have multiple consecutive hyphens.\n" + + " Github username cannot begin or end with a hyphen.\n" + + " Maximum is 39 characters and minimum of 2 characters.\n GitHub can be blank."; + + /* + * The first character of the gitHub must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[A-Za-z0-9][A-Za-z0-9-]{0,37}[A-Za-z0-9]"; + + public final String value; + + /** + * Constructs an {@code GitHub}. + * + * @param gitHub A valid gitHub. + */ + public GitHub(String gitHub) { + if (gitHub == null) { //if gitHub is empty it will exist as an empty string + value = ""; + } else { + checkArgument(isValidGitHub(gitHub), MESSAGE_CONSTRAINTS); + value = gitHub; + } + } + + /** + * Returns true if a given string is a valid gitHub. + */ + public static boolean isValidGitHub(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if the gitHub is null. + */ + public boolean isNull() { + return value.equals(""); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GitHub // instanceof handles nulls + && value.equals(((GitHub) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/student/Name.java similarity index 58% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/student/Name.java index 79244d71cf7..250fdcb14ed 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/student/Name.java @@ -1,22 +1,26 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names must contain letters. Can contain spaces, apostrophes and hyphens as long as they are in between " + + "letters. Consecutive spaces, apostrophes or hyphens are not allowed. " + + "Names must start with a letter. " + + "Numbers are allowed, but they must strictly be at the end of the name. " + + "Names cannot be blank and cannot be more than 100 characters long, including spaces."; /* * 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 = "[A-Za-z]([ '-]?[A-Za-z])*( \\d+)?"; public final String fullName; @@ -27,15 +31,15 @@ public class Name { */ public Name(String name) { requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + checkArgument(isValidName(name.trim()), MESSAGE_CONSTRAINTS); + fullName = name.trim(); } /** * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= 100; } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java similarity index 66% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java index c9b5868427c..848086784a4 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.student; import java.util.List; import java.util.function.Predicate; @@ -6,9 +6,9 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Student}'s {@code Name} matches all the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,10 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + public boolean test(Student student) { + String keys = String.join(" ", keywords); + String name = student.getName().fullName; + return StringUtil.containsPartialSentenceIgnoreCase(name, keys); } @Override diff --git a/src/main/java/seedu/address/model/student/Student.java b/src/main/java/seedu/address/model/student/Student.java new file mode 100644 index 00000000000..cb6964f7bb7 --- /dev/null +++ b/src/main/java/seedu/address/model/student/Student.java @@ -0,0 +1,183 @@ +package seedu.address.model.student; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Represents a Student in the address book. + * Guarantees: details except telegram and gitHub are present and not null field values are validated, immutable. + * Telegram and GitHub will be empty strings if the user command does not include them. + */ +public class Student implements Comparator { + + // Identity fields + private final Name name; + private final Telegram telegram; + private final Email email; + private final GitHub gitHub; + + // Data field + private final Set tutorialGroups = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Student(Name name, Telegram telegram, Email email, GitHub gitHub, Set tutorialGroups) { + requireAllNonNull(name, email, telegram, gitHub, tutorialGroups); + this.name = name; + this.telegram = telegram; + this.email = email; + this.gitHub = gitHub; + this.tutorialGroups.addAll(tutorialGroups); + } + + public Name getName() { + return name; + } + + public Telegram getTelegram() { + return telegram; + } + + public Email getEmail() { + return email; + } + + public GitHub getGitHub() { + return gitHub; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTutorialGroups() { + return Collections.unmodifiableSet(tutorialGroups); + } + + /** + * Returns true if both students have the same name. + * This defines a weaker notion of equality between two students. + */ + public boolean isSameStudent(Student otherStudent) { + if (otherStudent == this) { + return true; + } + + return otherStudent != null + && otherStudent.getName().equals(getName()); + } + + /** + * Checks if the tutorial group already exists + * + * @param toCheck is a set of tutorial Group(s) + * @return if any tutorial group exists under this Student + */ + public boolean tutorialGroupExists(Set toCheck) { + if (toCheck == null) { + return false; + } + for (TutorialGroup tgtc : toCheck) { + for (TutorialGroup tg : tutorialGroups) { + if (tgtc.equals(tg)) { + return true; + } + } + } + return false; + } + + /** + * Checks if the tutorial group already exists. Is case-insensitive + * + * @param toCheck is the tutorial group to check with + * @return if any tutorial group exists under this student + */ + public boolean tutorialGroupExists(TutorialGroup toCheck) { + if (toCheck == null) { + return false; + } + for (TutorialGroup tg : tutorialGroups) { + if (tg.equals(toCheck)) { + return true; + } + } + return false; + } + + /** + * Returns true if both students have the same identity and data fields. + * This defines a stronger notion of equality between two students. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student otherStudent = (Student) other; + return otherStudent.getName().equals(getName()) + && otherStudent.getTelegram().equals(getTelegram()) + && otherStudent.getEmail().equals(getEmail()) + && otherStudent.getGitHub().equals(getGitHub()) + && otherStudent.getTutorialGroups().equals(getTutorialGroups()); + } + + /** + * Compares its two arguments for order. Provide a way to sort the students by their name + * + * @param s1 the first student to be compared + * @param s2 the second student to be compared + * @return a negative integer, zero, or a positive integer corresponding to less than, equal to, or greater than + */ + @Override + public int compare(Student s1, Student s2) { + return s1.getName().toString().compareTo(s2.getName().toString()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, telegram, email, gitHub, tutorialGroups); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + builder.append(getName()) + .append("; Email: ") + .append(getEmail()); + + if (!getTelegram().isNull()) { + builder.append("; Telegram: ") + .append(getTelegram()); + } + + if (!getGitHub().isNull()) { + builder.append("; GitHub: ") + .append(getGitHub()); + } + + Set tutorialGroups = getTutorialGroups(); + assert !tutorialGroups.isEmpty(); + + builder.append("; Tutorial Groups: "); + tutorialGroups.forEach(builder::append); + + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/student/Telegram.java b/src/main/java/seedu/address/model/student/Telegram.java new file mode 100644 index 00000000000..477d118eef4 --- /dev/null +++ b/src/main/java/seedu/address/model/student/Telegram.java @@ -0,0 +1,62 @@ +package seedu.address.model.student; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's telegram in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidTelegram(String)} + */ +public class Telegram { + + public static final String MESSAGE_CONSTRAINTS = + "Telegram may begin with a \"{@}\" character, followed by between 5 to 32 alphanumerical " + + "characters, can be blank"; + public static final String VALIDATION_REGEX = "@?\\w{5,32}"; + public final String value; + + /** + * Constructs a {@code Telegram}. + * + * @param telegram A valid telegram. + */ + public Telegram(String telegram) { + if (telegram == null) { //if telegram is empty it will exist as an empty string + value = ""; + } else { + checkArgument(isValidTelegram(telegram), MESSAGE_CONSTRAINTS); + value = telegram; + } + } + + /** + * Returns true if a given string is a valid telegram. + */ + public static boolean isValidTelegram(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if the telegram is null. + */ + public boolean isNull() { + return value.equals(""); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Telegram // instanceof handles nulls + && value.equals(((Telegram) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/student/UniqueStudentList.java b/src/main/java/seedu/address/model/student/UniqueStudentList.java new file mode 100644 index 00000000000..7325620d1b6 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueStudentList.java @@ -0,0 +1,138 @@ +package seedu.address.model.student; + +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.student.exceptions.DuplicateStudentException; +import seedu.address.model.student.exceptions.StudentNotFoundException; + +/** + * A list of students that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code Student#isSameStudent(Student)}. + * As such, adding and updating of persons uses Student#isSameStudent(Student) for equality + * so as to ensure that the student being added or updated is unique in terms of identity in the UniqueStudentList. + * However, the removal of a student uses Student#equals(Object) so + * as to ensure that the student with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Student#isSameStudent(Student) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent student as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + /** + * Adds a student to the list. + * The student must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list. + * The student identity of {@code editedStudent} must not be the same as another existing student in the list. + */ + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameStudent(editedStudent) && contains(editedStudent)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedStudent); + } + + /** + * Removes the equivalent student from the list. + * The student must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new StudentNotFoundException(); + } + } + + public void setStudents(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setStudents(List students) { + requireAllNonNull(students); + if (!studentsAreUnique(students)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(students); + } + + /** + * 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) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean studentsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSameStudent(students.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java new file mode 100644 index 00000000000..d1a8f9ce635 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Students (Students are considered duplicates if they have the + * same identity). + */ +public class DuplicateStudentException extends RuntimeException { + public DuplicateStudentException() { + super("Operation would result in duplicate students"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java new file mode 100644 index 00000000000..2b41e9e0296 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified student. + */ +public class StudentNotFoundException extends RuntimeException {} 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 b0ea7e7dad7..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +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) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @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/tutorialgroup/TutorialGroup.java b/src/main/java/seedu/address/model/tutorialgroup/TutorialGroup.java new file mode 100644 index 00000000000..633cdd5323c --- /dev/null +++ b/src/main/java/seedu/address/model/tutorialgroup/TutorialGroup.java @@ -0,0 +1,84 @@ +package seedu.address.model.tutorialgroup; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a TutorialGroup in the address book. + * Guarantees: immutable; name is valid as declared in {@link #isValidTutorialGroupName(String)} + */ +public class TutorialGroup { + + public static final String MESSAGE_CONSTRAINTS = "Must STRICTLY consist of a module code, followed by a " + + "space, then the tutorial name. It should not be blank. The tutorial name must have a non-zero digit and " + + "cannot start or end with a hyphen. The name consist of letters or hyphens or digits or underscores." + + "Tutorial Group may only have a maximum of 100 characters including whitespace." + + "ALL letters in tutorial group inputs will be converted into uppercase letters."; + public static final String VALIDATION_REGEX = "[A-Za-z]{2,3}[1-8]\\d{3}[A-Za-z]{0,2} " + + "[\\w-]+"; // ensures correct module code and tutorial name contain letters or digits or underscores or + // hyphens + + public final String tutorialGroupName; + + /** + * Constructs a {@code TutorialGroup}. + * + * @param tutorialGroupName A valid tutorial group name. + */ + public TutorialGroup(String tutorialGroupName) { + requireNonNull(tutorialGroupName); + assert tutorialGroupName == tutorialGroupName.toUpperCase() : "tutorial group should be in uppercase"; + checkArgument(isValidTutorialGroupName(tutorialGroupName), MESSAGE_CONSTRAINTS); + this.tutorialGroupName = tutorialGroupName; + } + + /** + * Returns true if a given string is a valid tutorial group name. + */ + public static boolean isValidTutorialGroupName(String test) { + if (test.length() > 100 || !test.contains(" ")) { + return false; + } + String[] tutorialGroupSplitByOneWhitespace = test.split(" ", 2); + String tutorialName = tutorialGroupSplitByOneWhitespace[1]; + for (int i = 1; i <= 10; i++) { + if (tutorialName.contains(String.format("%d", i))) { + break; + } + if (i == 10) { + return false; + } + } + String lastCharacterOfTutorialName = tutorialName.substring(tutorialName.length() - 1); + String firstCharacterOfTutorialName = tutorialName.substring(0, 1); + if (!lastCharacterOfTutorialName.matches("[\\w]") + || !firstCharacterOfTutorialName.matches("[\\w]")) { + //ensures tutorial name does not start and end with hyphens + return false; + } + return test.matches(VALIDATION_REGEX); + } + + /** + * {@code TutorialGroup}s are equal if they have the same tutorial group name ignoring cases. + */ + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroup // instanceof handles nulls + && tutorialGroupName.equalsIgnoreCase(((TutorialGroup) other).tutorialGroupName)); // state check + } + + @Override + public int hashCode() { + return tutorialGroupName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + tutorialGroupName + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/tutorialgroup/TutorialGroupKeywordsPredicate.java b/src/main/java/seedu/address/model/tutorialgroup/TutorialGroupKeywordsPredicate.java new file mode 100644 index 00000000000..8827be8ba31 --- /dev/null +++ b/src/main/java/seedu/address/model/tutorialgroup/TutorialGroupKeywordsPredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.tutorialgroup; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.student.Student; + +/** + * Tests that a {@code Student}'s {@code Tutorial Group} matches any of the keywords given. + */ +public class TutorialGroupKeywordsPredicate implements Predicate { + private final String keywords; + + /** + * Constructor of the tutorial group keywords predicate class + * @param keywords need to match exactly to an existing tutorial group(case-insensitive) + */ + public TutorialGroupKeywordsPredicate(String keywords) { + assert(keywords != null) : "Invalid keywords"; + this.keywords = keywords; + } + + @Override + public boolean test(Student student) { + return student.getTutorialGroups().stream() + .anyMatch(tg -> StringUtil.containsFullSentenceIgnoreCase(tg.tutorialGroupName, keywords)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroupKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TutorialGroupKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..b6fc5f7296e 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,54 +6,54 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -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.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static Student[] getSampleStudents() { + return new Student[] { + new Student(new Name("Alex Yeoh"), new Telegram(null), new Email("alexyeoh@u.nus.edu"), + new GitHub("alex-yeoh"), + getTutorialGroupSet("CS2100 G01", "CS3230 T03")), + new Student(new Name("Bernice Yu"), new Telegram("bernice01"), new Email("berniceyu@u.nus.edu"), + new GitHub(null), + getTutorialGroupSet("CS2100 G01", "CS2106 T05")), + new Student(new Name("Charlotte Oliveiro"), new Telegram("@CharlotteO"), new Email("charlotte@u.nus.edu"), + new GitHub("charlotte-oliveiro"), + getTutorialGroupSet("CS2106 T05")), + new Student(new Name("David Li"), new Telegram(null), new Email("callmedavid@u.nus.edu"), + new GitHub(null), + getTutorialGroupSet("CS2100 G01")), + new Student(new Name("Irfan Ibrahim"), new Telegram("irfanIbrahim"), new Email("irfan@u.nus.edu"), + new GitHub("irfan-the-jet-plane"), + getTutorialGroupSet("CS2100 G01", "CS2106 T05")), + new Student(new Name("Roy Balakrishnan"), new Telegram("@royBalakrishnan"), new Email("royb@u.nus.edu"), + new GitHub("roy-balakrishnan"), + getTutorialGroupSet("CS2106 T05", "CS3230 T03")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Student sampleStudent : getSampleStudents()) { + sampleAb.addStudent(sampleStudent); } return sampleAb; } /** - * Returns a tag set containing the list of strings given. + * Returns a tutorial group set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getTutorialGroupSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) + .map(TutorialGroup::new) .collect(Collectors.toSet()); } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = 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("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @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 : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedStudent.java b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java new file mode 100644 index 00000000000..c956e491b75 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java @@ -0,0 +1,114 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.student.Email; +import seedu.address.model.student.GitHub; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.model.student.Telegram; +import seedu.address.model.tutorialgroup.TutorialGroup; + +/** + * Jackson-friendly version of {@link Student}. + */ +class JsonAdaptedStudent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; + public static final String NO_TUTORIAL_GROUP_MESSAGE_FORMAT = "Student has no tutorial group!"; + + private final String name; + private final String telegram; + private final String email; + private final String gitHub; + private final List inTutorialGroups = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedStudent} with the given student details. + */ + @JsonCreator + public JsonAdaptedStudent(@JsonProperty("name") String name, @JsonProperty("telegram") String telegram, + @JsonProperty("email") String email, @JsonProperty("gitHub") String gitHub, + @JsonProperty("tutorialGroup") List tutorialGroup) { + this.name = name; + this.telegram = telegram; + this.email = email; + this.gitHub = gitHub; + if (tutorialGroup != null) { + this.inTutorialGroups.addAll(tutorialGroup); + } + } + + /** + * Converts a given {@code Student} into this class for Jackson use. + */ + public JsonAdaptedStudent(Student source) { + name = source.getName().fullName; + email = source.getEmail().value; + telegram = source.getTelegram().value; + gitHub = source.getGitHub().value; + inTutorialGroups.addAll(source.getTutorialGroups().stream() + .map(JsonAdaptedTutorialGroup::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted student object into the model's {@code Student} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted student. + */ + public Student toModelType() throws IllegalValueException { + final List personTutorialGroups = new ArrayList<>(); + if (inTutorialGroups.isEmpty()) { + throw new IllegalValueException(NO_TUTORIAL_GROUP_MESSAGE_FORMAT); + } + for (JsonAdaptedTutorialGroup tag : inTutorialGroups) { + personTutorialGroups.add(tag.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (telegram == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Telegram.class.getSimpleName())); + } + if (!telegram.equals("") && !Telegram.isValidTelegram(telegram)) { + throw new IllegalValueException(Telegram.MESSAGE_CONSTRAINTS); + } + final Telegram modelTelegram = telegram.equals("") ? new Telegram(null) : new Telegram(telegram); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (gitHub == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, GitHub.class.getSimpleName())); + } + if (!gitHub.equals("") && !GitHub.isValidGitHub(gitHub)) { + throw new IllegalValueException(GitHub.MESSAGE_CONSTRAINTS); + } + final GitHub modelGitHub = gitHub.equals("") ? new GitHub(null) : new GitHub(gitHub); + + final Set modelTutorialGroups = new HashSet<>(personTutorialGroups); + return new Student(modelName, modelTelegram, modelEmail, modelGitHub, modelTutorialGroups); + } + +} 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/JsonAdaptedTutorialGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedTutorialGroup.java new file mode 100644 index 00000000000..eabde31f10f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTutorialGroup.java @@ -0,0 +1,48 @@ +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.tutorialgroup.TutorialGroup; + +/** + * Jackson-friendly version of {@link TutorialGroup}. + */ +class JsonAdaptedTutorialGroup { + + private final String tutorialGroupName; + + /** + * Constructs a {@code JsonAdaptedTutorialGroup} with the given {@code tagName}. + */ + @JsonCreator + public JsonAdaptedTutorialGroup(String tutorialGroupName) { + this.tutorialGroupName = tutorialGroupName; + } + + /** + * Converts a given {@code TutorialGroup} into this class for Jackson use. + */ + public JsonAdaptedTutorialGroup(TutorialGroup source) { + tutorialGroupName = source.tutorialGroupName; + } + + @JsonValue + public String getTutorialGroupName() { + return tutorialGroupName; + } + + /** + * Converts this Jackson-friendly adapted tutorial group object into the model's {@code TutorialGroup} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted tutorial group. + */ + public TutorialGroup toModelType() throws IllegalValueException { + if (!TutorialGroup.isValidTutorialGroupName(tutorialGroupName)) { + throw new IllegalValueException(TutorialGroup.MESSAGE_CONSTRAINTS); + } + return new TutorialGroup(tutorialGroupName); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..921fb608a5c 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,7 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * An Immutable AddressBook that is serializable to JSON format. @@ -19,16 +19,16 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_STUDENT = "Students list contains duplicate student(s)."; - private final List persons = new ArrayList<>(); + private final List students = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given students. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("students") List students) { + this.students.addAll(students); } /** @@ -37,7 +37,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay2122s2-cs2103t-w15-3.github.io/tp/UserGuide.html"; + public static final String HELP_MESSAGE = "Refer to the full user guide here: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @@ -27,6 +35,13 @@ public class HelpWindow extends UiPart { @FXML private Label helpMessage; + @FXML + private ScrollPane scrollPane; + + @FXML + private MarkdownView helpGuideView; + + /** * Creates a new HelpWindow. * @@ -34,6 +49,25 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); + + String helpGuide; + + try { + helpGuide = IOUtils.toString(HelpWindow.class + .getResourceAsStream("/help/helpGuide.md"), StandardCharsets.UTF_8); + } catch (NullPointerException | IOException e) { + // could not find helpGuide path + helpGuide = "File not found!"; + } + + //Solution for viewing markDown from third party library https://github.com/JPro-one/markdown-javafx-renderer + helpGuideView = new MarkdownView(helpGuide); + helpGuideView.getStylesheets().add("/com/sandec/mdfx/mdfx-default.css"); + helpGuideView.setPadding(new Insets(20)); + + scrollPane.setContent(helpGuideView); + scrollPane.setFitToWidth(true); + helpMessage.setText(HELP_MESSAGE); } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..e0710168204 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -31,7 +31,7 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private StudentListPanel studentListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -42,7 +42,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane studentListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -110,8 +110,8 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -163,8 +163,8 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public StudentListPanel getStudentListPanel() { + return studentListPanel; } /** diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * 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 Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StudentCard.java b/src/main/java/seedu/address/ui/StudentCard.java new file mode 100644 index 00000000000..d61a4820cff --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentCard.java @@ -0,0 +1,129 @@ +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import seedu.address.model.student.Student; + +/** + * A UI component that displays information of a {@code Student}. + */ +public class StudentCard extends UiPart { + + private static final String FXML = "StudentListCard.fxml"; + private static final String green = "#2E9675"; + + /** + * 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 Student student; + private final int imageLength = 30; + + @FXML + private HBox cardPane; + @FXML + private HBox telegramBox; + @FXML + private HBox gitHubBox; + @FXML + private HBox emailBox; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label email; + @FXML + private Label telegram; + @FXML + private Label gitHub; + @FXML + private ImageView telegramImage; + @FXML + private ImageView gitHubImage; + @FXML + private ImageView emailImage; + @FXML + private FlowPane tutorialGroups; + + private Image telegramLogo = new Image(this.getClass().getResourceAsStream("/images/TelegramLogo.png")); + private Image gitHubLogo = new Image(this.getClass().getResourceAsStream("/images/GithubLogo.png")); + private Image emailLogo = new Image(this.getClass().getResourceAsStream("/images/EmailLogo.png")); + + /** + * Creates a {@code PersonCode} with the given {@code Student} and index to display. + */ + public StudentCard(Student student, int displayedIndex) { + super(FXML); + this.student = student; + id.setText(String.valueOf(displayedIndex)); + name.setText(student.getName().fullName); + setContacts(telegramBox, telegramImage, telegramLogo, telegram, student.getTelegram().value); + setContacts(gitHubBox, gitHubImage, gitHubLogo, gitHub, student.getGitHub().value); + setContacts(emailBox, emailImage, emailLogo, email, student.getEmail().value); + student.getTutorialGroups().stream() + .sorted(Comparator.comparing(tutorialGroup -> tutorialGroup.tutorialGroupName)) + .forEach(tutorialGroup -> { + Label tgLabel = new Label(tutorialGroup.tutorialGroupName); + tgLabel.setBackground(getColorBackground(green)); + tgLabel.setPadding(new Insets(0, 5, 0, 5)); + tutorialGroups.getChildren().add(tgLabel); }); + } + + private void setContacts(HBox hb, ImageView iv, Image i, Label l, String s) { + if (s == null || s.isEmpty()) { + hb.setManaged(false); + iv.setManaged(false); + l.setManaged(false); + } else { + l.setText(s); + iv.setImage(i); + iv.setClip(getCircleClip(imageLength / 2)); + } + } + + private Circle getCircleClip(int radius) { + return new Circle(radius, radius, radius); + } + + private Background getColorBackground(String colorString) { + Paint paint = Paint.valueOf(colorString); + BackgroundFill bf = new BackgroundFill(paint, new CornerRadii(3, false), null); + return new Background(bf); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentCard)) { + return false; + } + + // state check + StudentCard card = (StudentCard) other; + return id.getText().equals(card.id.getText()) + && student.equals(card.student); + } +} diff --git a/src/main/java/seedu/address/ui/StudentListPanel.java b/src/main/java/seedu/address/ui/StudentListPanel.java new file mode 100644 index 00000000000..c906d3f82b3 --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; + +/** + * Panel containing the list of students. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "StudentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public StudentListPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ + class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentCard(student, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/resources/help/helpGuide.md b/src/main/resources/help/helpGuide.md new file mode 100644 index 00000000000..b487050efc0 --- /dev/null +++ b/src/main/resources/help/helpGuide.md @@ -0,0 +1,38 @@ +>*Note*: +>If you wish to copy-and-paste example commands, open up the full user guide +in a browser using the URL above. This window is for viewing only. + +## Command Summary + +| Action | Format, Examples | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Add Student** | `add n/NAME e/EMAIL [t/TELEGRAM] [g/GITHUB] tg/TUTORIAL_GROUP…`
e.g. `add n/John Smith tg/CS2103T W15-3 e/e0123456@u.nus.edu t/johnsmyname g/johnsmyname` | +| **Add Tutorial Group for Student** | `addtg INDEX tg/TUTORIAL_GROUP…`
e.g. `addtg 5 tg/CS2100 G08` | +| **Edit Student** | `edit INDEX [n/NAME] [e/EMAIL] [t/TELEGRAM] [g/GITHUB] [tg/TUTORIAL_GROUP]…`
e.g. `edit 3 n/Mary Sue t/PresentPerfect` | +| **Find Students by name** | `find KEYWORD [ADDTIONAL_KEYWORDS]`
e.g.`find Jack Ho` | +| **Find Students by Tutorial Group** | `findtg TUTORIAL_GROUP`
e.g. `findtg CS2101 G08` | +| **Delete Student** | `delete INDEX`
e.g. `delete 4` | +| **Deleting Tutorial Group from Student** | `deletetg INDEX tg/TUTORIAL_GROUP`
e.g. `deletetg 4 tg/cs2030s t11` | +| **Deleting Tutorial Group from all Students** | `deletetgall tg/TUTORIAL_GROUP…`
e.g. `deletetgall tg/CS2106 T08` | +| **List** | `list` | +| **Clear** | `clear` (**WARNING:** Action cannot be undone) | +| **Help** | `help` | + + +## Input Requirements + +There are **parameters** (e.g. Names, Emails, and GitHub usernames) that must follow certain +requirements so that TACH recognises them as valid parameters. Here is a list of requirements for every parameter to avoid invalid inputs. + +In the list, a **word** is defined as a bunch of *characters* (letters, numbers, punctuation, etc.) separated by spaces. +e.g. `There A_RE 4 w0-rd_s.` has 4 words. + +| Parameter | Requirements | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **INDEX** | Must be a positive number that can be a maximum of 2,147,483,647 (1, 2, 3, … , 2147483647).
e.g. `100` | +| **NAME** | Must contain letters. Can contain spaces, apostrophes and hyphens as long as they are in between letters. Consecutive spaces, apostrophes or hyphens are not allowed. Name must start with a letter. Numbers are allowed, but they must strictly be at the end of the name separated by a space. Cannot be empty, and must not be more than 100 characters long, including spaces.
e.g. `John Smith 2` or `Johnson-Johnson Maxine d'Arby` | +| **TUTORIAL_GROUP** | Must **STRICTLY** consist of a module code, followed by a space, then the tutorial name. It should not be blank. The tutorial name must have a non-zero digit and cannot start or end with a hyphen. The name can consist of a combination of letters, hyphens, digits, underscores. Tutorial groups can only have a maximum of 100 characters including spaces.
e.g. `CS2103T W15-3_A`

**ALL** letters in tutorial group inputs will be converted into uppercase letters
e.g. `addtg 2 tg/cs2101 t01` will add `CS2101 T01` into the list of tutorial groups of student 2, assuming there are at least 2 students in the list that you see. | +| **EMAIL** | An email consists of three parts in the following order, the local part, the `@` sign, and the domain part.
The local part should only contain letters, numbers, and these special symbols: `+` `_` `.` `-`. It cannot start or end with the special symbols. It must be at most 64 characters long.
There must be an @ sign afterwards, followed by the domain name.
The domain name must be one of the following: `u.nus.edu` `nus.edu.sg` `gmail.com` `yahoo.com` `outlook.com` `hotmail.com`
e.g. `e0123456@u.nus.edu` or `jasminelim@gmail.com`
(Our original intention is for you to put in your students' NUS emails which are given to you on LumiNUS, but we also provide the freedom to use some of the more popular email providers listed above, should they be more convenient.) | +| **TELEGRAM** | May include `@` at the start. Must be exactly one word that can contain letters, numbers and underscores. It must be between 5 to 32 characters long (inclusive). This does not count the `@` symbol.
e.g. `Dave3` or `@Lorem_ipsum_dolor_sit_amet_12345` | +| **GITHUB** | Must be exactly one word that can contain letters, numbers and hyphens. It must be between 2 to 39 characters long (inclusive).
e.g. `12345678` or `cake-is-a-lie77` | + diff --git a/src/main/resources/images/EmailLogo.png b/src/main/resources/images/EmailLogo.png new file mode 100644 index 00000000000..d7e53962b99 Binary files /dev/null and b/src/main/resources/images/EmailLogo.png differ diff --git a/src/main/resources/images/GithubLogo.png b/src/main/resources/images/GithubLogo.png new file mode 100644 index 00000000000..c24e8309e87 Binary files /dev/null and b/src/main/resources/images/GithubLogo.png differ diff --git a/src/main/resources/images/TelegramLogo.png b/src/main/resources/images/TelegramLogo.png new file mode 100644 index 00000000000..ebd8866a011 Binary files /dev/null and b/src/main/resources/images/TelegramLogo.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..3bebbb8182c 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -120,9 +120,16 @@ -fx-text-fill: white; } +.cell_index { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 24px; + -fx-text-fill: #010504; + -fx-background-color: #2D5691DD; +} + .cell_big_label { -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + -fx-font-size: 32px; -fx-text-fill: #010504; } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..48d82252096 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -5,11 +5,16 @@ + + - + + + @@ -18,27 +23,26 @@ - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..2560323a810 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,13 +6,12 @@ - + - + @@ -33,25 +32,24 @@ - + - + - + - + - + + - + - + - - + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index f08ea32ad55..00000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56..b58628a4dfb 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,6 @@ - -