diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6ff220b5196..ca85dcec3c1 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,7 +22,7 @@ jobs: run: git checkout --progress --force ${{ github.sha }} - name: Run repository-wide tests - if: runner.os == 'Linux' + if: runner.os == 'macOS' working-directory: ${{ github.workspace }}/.github run: ./run-checks.sh @@ -39,7 +39,7 @@ jobs: run: ./gradlew check coverage - name: Upload coverage reports to Codecov - if: runner.os == 'Linux' + if: runner.os == 'macOS' uses: codecov/codecov-action@v3 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index 13f5c77403f..84e17b6bb15 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,26 @@ -[![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/AY2324S1-CS2103T-F12-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-F12-2/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. +### StudentConnect + +* This project was created for the **CS2101 Effective Communication for Computing Professionals and CS2103T Software Engineering modules**.
+* It is named `StudentConnect` because it was created to connect students in CS2103T and help with the formation of teams. +* For the detailed documentation of this project, see the **[StudentConnect GitHub Website](https://github.com/AY2324S1-CS2103T-F12-2/tp)** and **[StudentConnect User Guide](https://ay2324s1-cs2103t-f12-2.github.io/tp/UserGuide.html)**. +* Value proposition:
+ * `StudentConnect` helps with the **formation of project teams for CS2101/CS2103T** + * `StudentConnect` **stores** and **organises students’ contact details, and project group** + * Students can **easily search for the profiles of other students** and **connect with potential teammates** + * It offers tools for **tracking project progress/deadlines**, ensuring that the group stays on task + * There is no other application like `StudentConnect` for **CS2101/CS2103T students** + +### Other Links +* StudentConnect [Main Website](https://ay2324s1-cs2103t-f12-2.github.io/tp/) +* StudentConnect [User Guide](https://ay2324s1-cs2103t-f12-2.github.io/tp/UserGuide.html) +* StudentConnect [Developer Guide](https://ay2324s1-cs2103t-f12-2.github.io/tp/DeveloperGuide.html) +* StudentConnect [About Us Website](https://ay2324s1-cs2103t-f12-2.github.io/tp/AboutUs.html) +* StudentConnect [Github Website](https://github.com/AY2324S1-CS2103T-F12-2/tp) + +### Acknowledgements +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + diff --git a/build.gradle b/build.gradle index a2951cc709e..73b0640b2ad 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,19 @@ checkstyle { toolVersion = '10.2' } +run { + enableAssertions = true +} + test { + doFirst { + if (System.getProperty("os.name").equals("Linux")) { + // FX tests do not successfully run on Ubuntu Runner + exclude '**/ui/**' + // Excluded because ClearCommand uses FX as well + exclude '**/ClearCommandTest.class' + } + } useJUnitPlatform() finalizedBy jacocoTestReport } @@ -56,17 +68,26 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'linux' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion + testImplementation 'org.testfx:testfx-core:4.0.16-alpha' + testImplementation 'org.testfx:testfx-junit5:4.0.16-alpha' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } +run { + enableAssertions = true +} + shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'studentconnect.jar' } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..528c472c056 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,55 @@ 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 from our [GitHub Website](https://github.com/AY2324S1-CS2103T-F12-2/tp). ## Project team -### John Doe +### Pearlynn Toh - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/PearlynnT)] +[[portfolio](team/pearlynnt.md)] -* Role: Project Advisor +* Role: Team Lead, Developer -### Jane Doe +### Yik Leong Loo - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/Bearypop)] +[[portfolio](team/bearypop.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Testing + Integration -### Johnny Doe +### Chan Wei Ning - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/wnchan)] +[[portfolio](team/wnchan.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Documentation -### Jean Doe +### Alnaseri, Majedah Talal M - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/maj0-0)] +[[portfolio](team/maj0-0.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Deliverables & Deadlines + Scheduling & Tracking -### James Doe +### Dewangan Neya Praveen - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/neyapraveen)] +[[portfolio](team/neyapraveen.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Code Quality diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..eb508ed1594 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -9,135 +9,163 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This project is based on the AddressBook-Level3 project created by the [**SE-EDU initiative**](https://se-education.org). -------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +Refer to the guide [**_Setting up and getting started_**](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- +
+ ## **Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the +[_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+
+ ### Architecture -The ***Architecture Diagram*** given above explains the high-level design of the App. +The ***Architecture Diagram*** given above explains the high-level design of the app. Given below is a quick overview of main components and how they interact with each other. **Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. -* At app launch, it initializes the other components in the correct sequence, and connects them up with each other. +**`Main`** (consisting of classes [`Main`](https://github.com/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/Main.java) +and [`MainApp`](https://github.com/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. +* At app launch, it initialises the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. The bulk of the app's work is done by the following four components: -* [**`UI`**](#ui-component): The UI of the App. +* [**`UI`**](#ui-component): The UI of the app. * [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. +* [**`Model`**](#model-component): Holds the data of the app in memory. * [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete johnd@u.nus.edu`. Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point). -For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. +For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the +`LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface +rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. The sections below give more details of each component. +
### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2324S1-CS2103T-F12-2/tp/blob/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 different parts e.g., `CommandBox`, `ResultDisplay`, `PersonListPanel`, +`StatusBarFooter`, `ConfirmationPopup` 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/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is +specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103T-F12-2/tp/blob/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 `Person` or `Group` 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/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete johnd@u.nus.edu")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete johnd@u.nus.edu` Command](images/DeleteSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
:information_source: **Note:** The lifeline for `DeleteCommandParser` +should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates +a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g., to delete a person). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a +placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse +the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface +so that they can be treated similarly where possible e.g, during testing. +
### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - - +**API** : [`Model.java`](https://github.com/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/model/Model.java) +
+ Person Model Class Diagram +
Person Model Class Diagram
+
+
+ Group Model Class Diagram +
Group Model Class Diagram
+
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 `Person` objects (which are contained in a `UniquePersonList` object), and all +`Group` objects (which are contained in a `UniqueGroupList` 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 currently 'selected' `Group` 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.
- - - -
- +* 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) +
### 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/AY2324S1-CS2103T-F12-2/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) - + The `Storage` component, * can save both address book data and user preference data in JSON format, and read them back into corresponding objects. @@ -149,98 +177,109 @@ The `Storage` component, Classes used by multiple components are in the `seedu.addressbook.commons` package. -------------------------------------------------------------------------------------------------------------------- +
## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +--- -#### Proposed Implementation +### Tutorial Field -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: +#### Current Implementation -* `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. +In response to the evolving needs of StudentConnect, we have introduced several essential enhancements to user profiles. These enhancements include the addition of fields for Tutorial, Nationality, and Gender. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The Tutorial field is a crucial addition to the StudentConnect application, providing users with the capability to specify their tutorial groups, which are integral to their group formation needs. Here, we detail how the Tutorial field is implemented: -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +- **Data Structure**: Tutorials are now stored as a Set of Tutorial objects within the Person model. The use of a Set ensures that no duplicate tutorials can be associated with a single user. The code snippet below demonstrates this implementation: -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. + ```java + Set tutorials = new HashSet(); + ``` -![UndoRedoState0](images/UndoRedoState0.png) +- **Tutorial Attributes**: Each Tutorial object possesses an attribute called "value," which is of type string. The "value" must adhere to a two-digit format, ranging from 01 to 22. -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. -![UndoRedoState1](images/UndoRedoState1.png) +- **Consistency**: The decision to enforce a two-digit format (e.g., T01, T02) was made for consistency, ensuring that all tutorials are consistently represented as "TXX," where XX corresponds to the tutorial group. -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. -![UndoRedoState2](images/UndoRedoState2.png) +- **Range**: The allowed range of values, spanning from 01 to 22, aligns with the number of available tutorial group options for AY2023/2024 SEM1. -
: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`. -
+- **Set vs. List**: Initially, the tutorials field was implemented as a List of Tutorial objects (`List`). However, we observed that this approach allowed for multiple duplicate tutorials to be input without triggering errors. To address this, we transitioned to using a Set of tutorials, which inherently disallows duplicate entries without the need for additional validation checks: -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. + ```java + // Initial implementation as a List + List tutorials = new ArrayList(); + ``` -![UndoRedoState3](images/UndoRedoState3.png) +--- -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +### List Tasks Feature -
+#### Current Implementation -The following sequence diagram shows how the undo operation works: +The list tasks feature is facilitated by the `TasksCommand` class and involves the cooperation of several other classes including `Group`, `TaskList`, and `Model`. This command is designed to list all tasks associated with a specific group in StudentConnect, which is particularly useful for managing tasks in courses like CS2103T and CS2101. -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +The process is initiated when the user inputs a command to list tasks associated with a group. Here's the breakdown of the steps and class interactions: -
: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. +1. **Input Parsing**: The user's input is first parsed by `AddressBookParser`, which identifies the command word and delegates the argument parsing to `TasksCommandParser`. -
+2. **Command Creation**: `TasksCommandParser` parses the provided arguments, checks for correctness, and if valid, constructs a `TasksCommand` object with the specified group number. -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. +3. **Command Execution**: `TasksCommand#execute()` is then called. It retrieves the specified group by number using `Model#getGroupWithNumber()`. If the group exists, it then retrieves the group's tasks using `Group#getTasks()`. -
: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. +4. **Task Initialisation**: If no tasks are currently set, `TasksCommand` initialises a default set of tasks through `TaskInitializer.initializeTasks()` to ensure that each group has tasks pre-set, reflecting the course's structure. -
+5. **Result Generation**: Finally, the tasks are converted to a String format and included in the `CommandResult` which then displays the tasks in the feedback panel. -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. +The `TasksCommand` class performs the following operations: +- `TasksCommand#execute()` - Lists all tasks for the specified group number. +- `TasksCommand#equals()` - Compares this `TasksCommand` with another command for equality, based on the group number. -![UndoRedoState4](images/UndoRedoState4.png) +The feature's utility is highlighted by its ability to list pre-set tasks automatically assigned to each group, reflecting the tasks specific to CS2103T and CS2101 courses. -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. +#### Proposed Future Implementation -![UndoRedoState5](images/UndoRedoState5.png) +The future enhancements for the Tasks Command aim to significantly improve user interaction and task management within the application. The proposed improvements will enable users to not only list tasks but also add, edit, and delete tasks as per their requirements. This will provide users with full control over how they manage the task workflow for each group, making the application more flexible and user-friendly. -The following activity diagram summarizes what happens when a user executes a new command: +Additionally, we plan to develop a dedicated panel within the User Interface (UI) specifically for tasks. This dedicated task panel will allow users to view all tasks in a separate, focused area of the UI, making task management more organized and less cluttered. With a dedicated panel, the tasks will not only be more visible but can also be interacted with in a more intuitive way. Users will be able to see at a glance all the tasks for a group, check their completion status, and access task details with a single click. - +--- -#### Design considerations: +### Create group feature -**Aspect: How undo & redo executes:** +#### Implementation -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +The create group feature is implemented via the `CreateCommand` class and involves the following classes: `Group`, `UniqueGroupList`, `JsonAdaptedGroup`, `AddressBook`, `JsonSerializableAddressBook`. +
+`CreateCommand` implements the following operations: +* `CreateCommand#execute()` — Creates a new empty group using the group number generated from `generateGroupNumber`. +* `CreateCommand#generateGroupNumber()` — Generates the next available group number. -* **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. +Given below is an example usage scenario and how the create feature behaves at each step. -_{more aspects and alternatives to be added}_ +Step 1. The user types in "create t/01". The string, "t/01", is parsed by `CreateCommandParser`, which returns a new instance of `CreateCommand`. +
+Step 2. The command is then executed by `CreateCommand#execute()`. `CreateCommand#execute()` calls `CreateCommand#generateGroupNumber()`. +
+Step 3. `CreateCommand#generateGroupNumber()` generates the next available group number, which is the next largest number that has not been assigned to a group. It loads in the current state of the Address Book via `AddressBook#getAddressBook`. It then iterates over the `Group` list inside the Address Book and checks the numbers of the groups to determine the next available group number. +
+Step 4. `CreateCommand#execute()` creates a new `Group` using the generated group number and adds it to the Address Book via `AddressBook#addGroup`. +
+Step 5. The `CommandResult` containing the success message is shown to the user. -### \[Proposed\] Data archiving +Shown below is the sequence diagram for the given scenario. The `LogicManager`, `AddressBookParser` and `Model` classes are also included to give a complete picture of the process. -_{Explain here how the data archiving feature will be implemented}_ +![CreateCommand sequence diagram](images/CreateSequenceDiagram.png) +
:information_source: **Note:** The lifeline for `CreateCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
-------------------------------------------------------------------------------------------------------------------- - +
## **Documentation, logging, testing, configuration, dev-ops** * [Documentation guide](Documentation.md) @@ -257,42 +296,73 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +* CS2103T student +* Face challenges in finding like-minded students for group projects +* Busy academic schedule +* Need assistance in keeping track of project deadlines, tasks, and progress +* Can type fast +* Prefer desktop apps over other types +* Prefers typing to mouse interactions -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: StudentConnect stores and organises students’ contact details, and project group. +Students can easily search for the profiles of other students and connect with potential teammates. +This streamlines the process of forming project teams. It also offers tools for tracking project +progress/deadlines, ensuring that the group stays on task. There is no other application quite like StudentConnect for +CS2103T students. ### 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…​ | +|----------|--------------------|----------------------------------------------------------------------|---------------------------------------------------| +| `* * *` | student | add my personal details to the system | get other students to learn more about me | +| `* * *` | student | view the rest of the students in the course | see my options for choosing teammates | +| `* * *` | student | see the other student’s name, major,tutorial slots, basic info, etc. | make informed decisions | +| `* * *` | student | see the nationality and gender of other students | meet the criteria when forming a group | +| `* * *` | student | customise and update my profile details | ensure that my profile is up-to-date | +| `* * *` | student | view other students in the same tutorial group | communicate with them | +| `* * *` | student | remove my personal details from the system | stop using the application | +| `* * *` | student | create a group on the app | form a group for the course | +| `* * *` | student | join a group on the app | form a group for the course | +| `* *` | student | seek help and check requirements for CS2101/CS2103T groupings | be more clear of the valid group formations | +| `* * ` | student | be able to click the links of the social media to view them | avoid wasting time typing links manually | +| `* * ` | student | be able to leave a group on the app | join another group of my choice | +| `* * ` | student | be able to delete a group I created on the app | get rid of unnecessary groups on the system | +| `* * ` | student | be able to view my courses' deadlines and tasks | keep track of my projects' deliverables | +| `* * ` | student | be able to mark a task as complete | prioritise other tasks | +| `* * ` | student | be able to unmark a task as incomplete | prioritise on completing it | +| `* * *` | course coordinator | be able to remove all the data from the system | reuse the application for new batches of students | +| `* * *` | course coordinator | be asked to confirm if I want to clear all the data | prevent accidentally clearing all the data | +| `* * *` | user | exit the app | close the app | ### 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 `StudentConnect` and the Actor is the `student`, unless specified otherwise) + +**Use case: UC1 - Add a student** + +**MSS** + +1. Student requests to add his/her personal information. +2. StudentConnect adds the student’s information into the list. + + Use case ends. -**Use case: Delete a person** +**Extensions** + +* 2a. Input is invalid. + * 2a1. StudentConnect displays error message. + + Use case ends. + +**Use case: UC2 - List students** **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. Student requests to list students. +2. StudentConnect shows a list of students. Use case ends. @@ -302,35 +372,388 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 3a. The given index is invalid. +**Use Case: UC3 - Access social media** + +**MSS** - * 3a1. AddressBook shows an error message. +1. Student requests to list students (UC2). +2. Student clicks on social media link of a person. +3. StudentConnect opens the social media link in the browser. - Use case resumes at step 2. + Use case ends. -*{More to be added}* +**Extensions:** -### Non-Functional Requirements +* 1a. Student requests to find a student instead. + * Use case resumes from step 2. + +* 3a. The website linked does not exist. + * 3a1. Browser shows that corresponding link does not exist. + + Use case ends. + +* 3b. Hyperlink does not exist + * 3b1. StudentConnect does not redirect to the browser. + + Use case ends. + +**Use Case: UC4 - Edit a student** + +**MSS** + +1. Student requests to update a specific student’s details on the list by email. +2. StudentConnect shows a list of students containing the student with the updated details. + + Use case ends. + +**Extensions:** + +* 1a. The given email is invalid. + * 1a1. StudentConnect shows an error message. + + Use case ends. + +**Use Case: UC5 - Find a student** + +**MSS** + +1. Student requests to find student(s) by name with keyword(s). +2. StudentConnect shows a list of students whose name(s) contain the keyword(s). + + Use case ends. + +**Extensions:** + +* 1a. The given keyword(s) does not match any of the students’ names. + + Use case ends. + +**Use Case: UC6 - Filter students** + +**MSS** + +1. Student requests to filter students by tutorial slot(s). +2. StudentConnect shows a list of students whose tutorial(s) contain the given slot. + + Use case ends. + +**Extensions:** +* 1a. The given tutorial slot(s) is invalid. + * 1a1. StudentConnect shows an error message. + +* 1b. The given slot(s) does not match any of the students' tutorial slots. + + Use case ends. + +**Use Case: UC7 - Delete a student** -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. +**MSS** + +1. Student requests to delete a specific student on the list by email. +2. StudentConnect deletes the student. + + Use case ends. + +**Extensions:** + +* 1a. The given email is invalid. + * 1a1. StudentConnect shows an error message. + + Use case ends. + +**Use Case: UC8 - Create a group** + +**MSS** + +1. Student requests to create a group by providing a tutorial number. +2. StudentConnect creates a new empty group. + + Use case ends. + +**Extensions:** + +* 1a. The given tutorial number is invalid. + * 1a1. StudentConnect shows an error message. + + Use case ends. + +**Use Case: UC9 - List all groups** + +**MSS** + +1. Student requests to list groups. +2. StudentConnect shows a list of groups. + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + +**Use Case: UC10 - Join a group** + +**MSS** + +1. Student requests to join a group by providing their email and the group number. +2. StudentConnect adds the student to the group in the system. + + Use case ends. + +**Extensions:** + +* 1a. The specified group does not exist. + * 1a1. StudentConnect shows an error message. + + Use case ends. + +* 1b. The specified student is not found in the system. + * 1b1. StudentConnect shows an error message. + + Use case ends. + +* 1c. The specified group is full. + * 1c1. StudentConnect shows an error message. + + Use case ends. + +* 1d. The specified student is already in the specified group. + * 1d1. StudentConnect shows an error message. + + Use case ends. + +* 1e. The specified student is already in another group. + * 1e1. StudentConnect shows an error message. + + Use case ends. + +**Use Case: UC11 - Delete a group** + +**MSS** + +1. Student requests to delete a specific group. +2. StudentConnect deletes the particular group. + + Use case ends. + +**Extensions:** + +* 1a. The specified group does not exist. + * 1a1. StudentConnect states the corresponding group does not exist. + + Use case ends. + +**Use Case: UC12 - Leave a group** + +**MSS** + +1. Student requests to leave a specific group. +2. StudentConnect removes their details from the particular group. + + Use case ends. + +**Extensions:** + +* 1a. The specified group does not exist. + * 1a1. StudentConnect states the corresponding group does not exist. + + Use case ends. + +* 1b. The student is not in the specified group. + * 1b1. StudentConnect notifies the student that they are not in the group. + + Use case ends. + +**Use Case: UC13 - Find a group** + +**MSS** -*{More to be added}* +1. Student requests to find group(s) by group number with keyword(s). +2. StudentConnect shows a list of group(s) with group number(s) matching the keyword(s). + Use case ends. + +**Extensions:** +* 1a. The given keyword(s) is invalid. + * 1a1. StudentConnect shows an error message. +* 1b. The given keyword(s) does not match any of the group numbers. + + Use case ends. + +**Use Case: UC14 - Filter groups** + +**MSS** + +1. Student requests to filter groups by tutorial slot. +2. StudentConnect shows a list of groups that belong to the given tutorial slot. + + Use case ends. + +**Extensions:** +* 1a. The given tutorial slot is invalid. + * 1a1. StudentConnect shows an error message. +* 1b. The given tutorial slot does not match any of the groups' tutorial slot. + + Use case ends. + +**Use Case: UC15 - Check a group** + +**MSS** + +1. Student requests to check if a particular group meets the requirements for group formation by group number. +2. StudentConnect displays a message stating if the specified group meets the requirement. + + Use case ends. + +**Extensions:** +* 1a. The specified group is not registered in the system. + * 1a1. StudentConnect shows an error message. +* 1b. The group number is invalid. + * 1b1. StudentConnect shows an error message. + + Use case ends. + +**Use Case: UC16 - List Tasks** + +**MSS** + +1. Student requests to list tasks of a specific group. +2. Student provides the group number. +3. StudentConnect retrieves the group by the given group number. +4. If the group exists, StudentConnect displays the group in the group panel. +5. StudentConnect displays the listed tasks along with a success message.
+ Use case ends. + +**Extensions:** + +* 2a. The student enters a non-existent group number. + * 2a1. StudentConnect shows an error message: "Group with the provided group number not found."
+ Use case ends. + +* 2b. The student enters an invalid group number. + * 2b1. StudentConnect shows an error message: "Invalid command format!"
+ Use case ends. + +* 4a. The specified group exists but has no tasks initialised. + * 4a1. StudentConnect initialises the tasks for the group. + * 4a2. StudentConnect displays the newly initialised tasks along with a success message.
+ Use case resumes at step 5. + +* 4b. Task initialisation fails due to a system error. + * 4b1. StudentConnect displays an error message indicating a failure in task initialisation. + Use case ends. + +**Use Case: UC17 - Mark Task as Done** + +**MSS** + +1. Student requests to mark a task of a group as done. +2. Student provides the group number and task index. +3. StudentConnect marks the specified task as done. +4. StudentConnect displays a success message. + + Use case ends. + +**Extensions:** + +* 2a. The specified group does not exist. + * 2a1. StudentConnect shows an error message: "Group with the provided group number not found."
+ Use case ends. + +* 2b. The specified task index is invalid. + * 2b1. StudentConnect shows an error message: "Invalid task index. Task not found."
+ Use case ends. + +**Use Case: UC18 - Mark Task as Not Done** + +**MSS** + +1. Student requests to unmark a task that was marked as done. +2. Student provides the group number and task index. +3. StudentConnect marks the specified task as not done. +4. StudentConnect displays a success message. + + Use case ends. + +**Extensions:** + +* 2a. The specified group does not exist. + * 2a1. StudentConnect shows an error message: "Group with the provided group number not found."
+ Use case ends. + +* 2b. The specified task index is invalid. + * 2b1. StudentConnect shows an error message: "Invalid task index. Task not found."
+ Use case ends. + +**Use Case: UC19 - Get help** + +**MSS** + +1. Student requests to see requirement list or user guide by typing “help”. +2. StudentConnect displays the help message, along with the requirement message and a link to the user guide. + + Use case ends. + +**Use case: UC20 - Clear all data** + +**MSS** + +1. User requests to clear data. +2. StudentConnect shows a pop-up asking for confirmation. +3. User confirms. +4. StudentConnect clears all data from the system. + + Use case ends. + +**Extensions** + +* 3a. User cancels clearing the data. + * 3a1. StudentConnect cancels the process and returns to main window. + + Use case ends. + +* 3a. User does not respond. + + Use case ends. + +**Use Case: UC21 - Exit the app** + +**MSS** + +1. Student requests to delete the app by typing “exit”. +2. StudentConnect displays the goodbye message. +3. StudentConnect closes. + + Use case ends. + +### 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 students without a noticeable sluggishness in performance for typical usage. +3. A student 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. + +
### Glossary * **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **CS2101**: Effective Communication for Computing Professionals - An NUS course designed to equip computing professionals with essential communication skills, both in technical and non-technical contexts. It covers the creation of clear and comprehensible software documentation and effective communication strategies for diverse audiences. +* **CS2103T**: Software Engineering - An NUS course focusing on the systematic and rigorous development of software systems. It covers essential concepts and analytical tools necessary for software engineering. +* **Command Terminal**: An interface in a computing environment where you can input text commands to perform specific tasks. Some examples include "Terminal" on macOS and "Powershell" on Windows. +* **GUI (Graphical User Interface)**: A type of user interface that allows you to interact with electronic devices through graphical icons and visual indicators, as opposed to text-based interfaces, typed command labels, or text navigation. +* **Home Folder**: In computing, this is a personal directory assigned to you in a file system, where they store personal files, settings, and configurations. +* **Java 11**: A version of Java, a widely used programming language and computing platform. Java 11 includes various updates and features different from its predecessors. -------------------------------------------------------------------------------------------------------------------- +
## **Appendix: Instructions for manual testing** Given below are instructions to test the app manually.
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. +testers are expected to do more *exploratory* testing. Testers can refer to the [**User Guide**](https://ay2324s1-cs2103t-f12-2.github.io/tp/UserGuide.html) +to check out more ways to test the app.
@@ -338,40 +761,226 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Ensure you have Java `11` or above installed in your Computer. + + 2. Download the latest `studentconnect.jar` from here. + + 3. Copy the file to the folder you want to use as the _home folder_ for your StudentConnect. + + 4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar studentconnect.jar` command to run the application.
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 5. Expected: Shows the GUI with a set of sample data. The window size may not be optimum. -1. Saving window preferences + ![Ui](images/Ui.png) + +2. Saving window preferences 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 2. Re-launch the app by repeating step 4 of the Initial launch.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +3. Shutting down + + 1. Input command `exit` into the CLI of the StudentConnect app.
+ Alternatively, click the `File` button, followed by the `Exit` button. + + 2. Expected: The `exit` message is shown and the app closes.
+ Exception: When using the `exit` button to close the app, the `exit` message is not shown. + +### General Commands + +1. Viewing help and group requirements + + 1. Test case: `help`.
+ Expected: Opens help window which shows the help message, `Copy Url` button and group requirements message. + + 2. Test case: `help X`.
+ `X` is any input written after `help`.
+ Expected: Similar to previous. + +2. Clearing all entries + + 1. Test case: `clear` and clicking `Yes`.
+ Expected: Opens confirmation window and successfully clears all entries. + + 2. Test case: `clear` and clicking `Cancel`.
+ Expected: Opens confirmation window and entries are not cleared. + + 3. Test case: `clear X`.
+ `X` is any input written after `clear`.
+ Expected: Similar to test case 2.1. + +3. Exiting the program + + 1. Similar to the shutting down case mentioned above. + +### Student Commands + +1. Adding a student + + 1. Test case: `add n/John Doe m/Computer Science y/2 e/johnd@u.nus.edu d/I’m a Frontend Developer t/06 t/19 sm/https://www.linkedin.com/in/john-doe-123456789 nt/local g/m`.
+ Expected: John doe added to student list. + + 2. Test case: `add X`.
+ `X` is any input that does not follow format: `n/NAME m/MAJOR y/YEAR e/EMAIL d/DESCRIPTION [t/TUTORIALS]… [sm/SOCIALMEDIA]… nt/NATIONALITY g/GENDER`.
+ Note: Social media and tutorial are optional.
+ Note: Please refer to the [**User Guide**](https://ay2324s1-cs2103t-f12-2.github.io/tp/UserGuide.html#adding-a-student--add) for information on the valid fields' values.
+ Expected: Error details shown in the status message. + +2. Editing a student + + 1. Test case: `edit johnd@u.nus.edu y/3 e/johndoe@u.nus.edu`.
+ Expected: Student with above email has year changed to `3` and email changed to `johndoe@u.nus.edu`. + + 2. Test case: `edit X`.
+ `X` is any input that does not follow format: `EMAIL [n/NAME] [m/MAJOR] [y/YEAR] [e/EMAIL] [d/DESCRIPTION] [t/TUTORIALS]…​ [sm/SOCIALMEDIA]…​ [nt/NATIONALITY] [g/GENDER]`.
+ Note: Fields in `[]` are optional.
+ Expected: Error details shown in the status message. + +3. Deleting a student + + 1. Test case: `delete alexyeoh@u.nus.edu`.
+ Expected: Student with above email deleted from the list. Details of the deleted student shown in the status message. -### Deleting a person + 2. Test case: `delete 0`.
+ Expected: No student is deleted. Error details shown in the status message. Status bar remains the same. -1. Deleting a person while all persons are being shown +4. Listing all students + + 1. Test case: `list`.
+ Expected: List students in StudentConnect. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 2. Test case: `list X`.
+ `X` is any input written after `list`.
+ Expected: Similar to previous. + +5. Finding a student by name + + 1. Test case: `find John`.
+ Expected: List students with `john` in their name. If no students found, no students will be listed. + + 2. Test case: `find john betsy`.
+ Expected: List students with `john` or `betsy` in their name. If no students found, no students will be listed. + +6. Filtering students by tutorial + + 1. Test case: `filter 10`.
+ Expected: List students with `T10` as their chosen tutorial. If no students found, no students will be listed. + + 2. Test case: `filter 12 15`.
+ Expected: List students with `T12` or `T15` as their chosen tutorial. If no students found, no students will be listed. + + 3. Test case: `filter 0` or `filter 23`.
+ Expected: Error details shown in status message as command only accepts tutorial groups from 01 to 22 inclusive. + +### Group Commands + +1. Creating a new group - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1. Test case: `create t/10`.
+ Expected: Create a new empty group with `T10` as its tutorial group. + + 2. Test case: `create t/0` or `create t/23`.
+ Expected: Error details shown in status message as command only accepts tutorial groups from 01 to 22 inclusive. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +2. Listing all groups - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Test case: `listGroup`.
+ Expected: List all groups in StudentConnect. + + 2. Test case: `listGroup X`.
+ `X` is any input written after `listGroup`.
Expected: Similar to previous. -1. _{ more test cases …​ }_ +3. Deleting a group + + 1. Test case: `deleteGroup gr/1`.
+ Expected: Group with above group number deleted from the group list. + + 2. Test case: `deleteGroup 0`.
+ Expected: No group is deleted. Error details shown in the status message. + +4. Joining a group + + 1. Test case: `join e/johnd@u.nus.edu gr/1`.
+ Expected: If there is lesser than 5 people in group 1, John Doe joins the group. Group list is displayed. + + 2. Test case: `join e/johnd@u.nus.edu gr/2`.
+ Expected: Since John Doe is already in a group (as mentioned in the previous test case), error details will be shown in the status message. + +5. Leaving a group + + 1. Test case: `leave e/johnd@u.nus.edu gr/1`.
+ Expected: If John Doe is in the group 1, his details will be removed from the group. + + 2. Test case: `leave e/johnd@u.nus.edu gr/2`.
+ Expected: Since John Doe is not a member of group 2, error details will be shown in the status message. + +6. Finding group by group number + + 1. Test case: `findGroup 7`.
+ Expected: Group `7` is listed. If the group exists, it will be listed. + 2. Test case: `findGroup 1 3`.
+ Expected: Group `1` and `3` listed. If the group exists, it will be listed. + + 3. Test case: `findGroup -1`.
+ Expected: Error details will be shown in the status message as group number needs to be a non-zero unsigned integer. + +7. Filtering group by tutorial + + 1. Test case: `filterGroup 10`.
+ Expected: List groups with `T10` as their tutorial slot. If no groups found, no groups will be listed. + + 2. Test case: `filterGroup 12 15`.
+ Expected: List groups with `T12` or `T15` as their tutorial slot. If no groups found, no groups will be listed. + + 3. Test case: `filterGroup 0` or `filterGroup 23`.
+ Expected: Error details shown in status message as command only accepts tutorial groups from 01 to 22 inclusive. + +8. Checking a group + + 1. Test case: `checkGroup 4`.
+ Expected: Checks if group 4 fulfils requirements mentioned in the `help` window. Returns message of requirements that are and are not fulfilled. + + 2. Test case: `checkGroup a`.
+ Expected: Error details shown in status message as command only accepts non-zero unsigned integers. + +9. Listing all tasks + + 1. Test case: `tasks 5`.
+ Expected: List all tasks of group 5. + + 2. Test case: `tasks a`.
+ Expected: Error details shown in status message as command only accepts non-zero unsigned integers. + +10. Marking a task as done + + 1. Test case: `mark gr/1 ti/1`.
+ Expected: If group 1 exists, marks task 1 as done. List tasks of the group 1 in the status message. + + 2. Test case: `mark gr/1 ti/15`.
+ Expected: Error details shown in status message as only 13 tasks are being initialised for all groups (mentioned in Point 4 of List Task Feature). + +11. Marking a task as not done + + 1. Test case: `unmark gr/1 ti/1`.
+ Expected: If group 1 exists, marks task 1 as not done. List tasks of the group 1 in the status message. + + 2. Test case: `unmark gr/1 ti/14`.
+ Expected: Error details shown in status message as only 13 tasks are being initialised for all groups (mentioned in Point 4 of List Task Feature). + +
### Saving data -1. Dealing with missing/corrupted data files +1. Students' data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +### Editing the data file -1. _{ more test cases …​ }_ +1. Students' data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. + 1. Advanced users are welcome to update data directly by editing that data file. + +
:exclamation: **Caution:** +If your changes to the data file makes its format invalid, StudentConnect will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +
diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..3e18aee7b13 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -45,7 +45,7 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [StudentConnect’s architecture](DeveloperGuide.md#architecture). 1. **Do the tutorials** These tutorials will help you get acquainted with the codebase. diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..41354909c4c 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -32,5 +32,5 @@ This project has three types of tests: e.g. `seedu.address.commons.StringUtilTest` 1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
e.g. `seedu.address.storage.StorageManagerTest` -1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
+1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how they are connected together.
e.g. `seedu.address.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 57437026c7b..fef15a5cbce 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,42 +2,140 @@ layout: page title: User Guide --- +
+ + Logo -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. + +

StudentConnect

+
+ +

Welcome to the StudentConnect User Guide! This comprehensive manual is designed to assist you in navigating the features of StudentConnect, a hassle-free team formation solution for CS2103T and CS2101 students to browse profiles and connect for the team project. +Whether you're new to the platform or looking to enhance your understanding, this guide is tailored to meet your needs. +To ensure a seamless experience, we recommend referring to the How to Use This Guide section before you get started. This section provides valuable insights on navigating the document, understanding icons and formatting, and optimising your overall journey.

+ +
-* Table of Contents -{:toc} +## Table of Contents + +--- + + +1. How to Use This Guide +2. Quick Start +3. Features + 1. General Commands + 1. Viewing Help : `help` + 2. Clearing All Entries : `clear` + 3. Exiting The Program : `exit` + 2. Student Commands + 1. Adding a Student : `add` + 2. Listing All Students : `list` + 3. Editing a Student : `edit` + 4. Finding a Student by Name : `find` + 5. Filtering Students by Tutorial : `filter` + 6. Deleting a Student : `delete` + 3. Group Commands + 1. Creating a New Group : `create` + 2. Listing All Groups : `listGroup` + 3. Joining a Group : `join` + 4. Deleting a Group : `deleteGroup` + 5. Leaving a Group : `leave` + 6. Finding a Group by Group Number : `findGroup` + 7. Filtering Groups by Tutorial : `filterGroup` + 8. Checking a Group : `checkGroup` + 9. Listing All Tasks : `tasks` + 10. Marking a Task as Done : `mark` + 11. Marking a Task as Not Done : `unmark` +4. Saving The Data +5. Editing The Data File +6. FAQ +7. Known Issues +8. Command Summary +9. Appendix +10. Glossary -------------------------------------------------------------------------------------------------------------------- +
-## Quick start +## How to Use This Guide -1. Ensure you have Java `11` or above installed in your Computer. +This guide is designed to help you navigate the features of StudentConnect with ease. +- To assist you in understanding and using this manual effectively, we've included a symbol table that outlines the icons and notations you'll encounter throughout the document. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +| Symbol | Type | Description | +|----------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------| +| :information_source: | Information | This icon indicates additional information or context that can help you understand the features or requirements better. | +| :bulb: | Tip | Look for this symbol when you need helpful suggestions on using StudentConnect features more efficiently. | +| :exclamation: | Important | This alerts you to critical information or functionality within StudentConnect that you should pay close attention to. | -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +- Throughout this guide, we've provided screenshots that illustrate the expected output for each command and feature. This visual aid is designed to enhance your understanding and ensure that you can confidently anticipate the results of your actions. Below is a detailed overview of the various interface components that you'll become familiar with. -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: +
+ Student List View of StudentConnect +
Figure 1: Student List View of StudentConnect
+
- * `list` : Lists all contacts. +
+ Group List View of StudentConnect +
Figure 2: Group List View of StudentConnect
+
- * `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. +1. **Command Box**: This is where you can enter any command for execution. After typing your command, press Enter to submit. If there is a syntax error or the command is unrecognisable, the text will turn red, prompting you to correct the command according to the specified requirements before pressing Enter again. +2. **Feedback Box**: The feedback box is the area where the application communicates responses to your commands. It displays the correct command format if there is an input error, error messages when a command fails to execute, and the outcomes for commands like the `tasks` command. +3. **Student List**: The student list panel displays a roster of all students and their associated details. Executing any student-related command will result in this panel being updated to show the relevant student list. +4. **Group List**: Similar to the student list, the group list panel shows all existing groups along with their members and designated tutorials. Group-related commands will update this panel accordingly. +5. **Menu Options**: The menu options provide quick access to general tasks within the application. Note that no messages are displayed in the feedback box when using the menu options. +
:exclamation: **Important:** +To guarantee that all your changes and data are saved correctly, it is strongly advised to type the `exit` command into the Command Box, +instead of selecting `exit` from the menu options or closing the application window directly. +This practice helps prevent data loss and ensures a proper shutdown of the application. +
- * `delete 3` : Deletes the 3rd contact shown in the current list. +- For convenient navigation throughout this UG, you can refer to the table of contents. +- Should you encounter any terminology within the guide that is unfamiliar, you can consult the glossary section below. It provides clear definitions and explanations to help you grasp the specialised language used throughout this guide. +- For any inquiries, the faq section is a good place to look for answers. In case your question is not addressed there, do not hesitate to submit an issue here for further assistance. +- The appendix section is included to detail all possible input values for fields that have specific constraints or require particular formats. This reference can help ensure that you input data correctly and understand the limitations of each field. +- For a quick and compact overview of all the commands that StudentConnect offers, refer to the command summary. This section servers as a handy reference for all possible actions you can perform within the application, laid out in an easily digestible format. - * `clear` : Deletes all contacts. +> Back to Table of Contents +
+
- * `exit` : Exits the app. +## Quick Start -1. Refer to the [Features](#features) below for details of each command. +1. Ensure you have Java `11` or above installed in your Computer. If not, you can download Java `11` from here. --------------------------------------------------------------------------------------------------------------------- +2. Download the latest `studentconnect.jar` from here. + +3. Copy the file to the folder you want to use as the home folder for your StudentConnect. + +4. Open a command terminal, and type `cd` to change the current working directory into the folder you put the jar file in. Use the `java -jar studentconnect.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+ ![Ui](images/Ui.png) + +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+ Some example commands you can try: + + * `list` : + * Lists all students in the StudentConnect system. + * `add n/John Doe m/Computer Science y/2 e/johnd@u.nus.edu d/I love programming in my free time t/02 t/17 t/20 sm/https://www.linkedin.com/in/john-doe-123456789 nt/local g/m` : + * Adds a student named `John Doe` and his details to the StudentConnect system. + * `delete EMAIL` : + * Deletes the student with the corresponding email. + * `create t/03` : + * Creates a new group assigned to tutorial 3. + * `clear` : + * Deletes all data from the system (i.e. students, groups, and tasks). + * `exit` : + * Exits the app. +6. Refer to the Features below for details of each command. + +> Back to Table of Contents +
+
## Features @@ -45,153 +143,1074 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo **:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+* 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 as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g. `n/NAME [t/TUTORIALS]` can be used as `n/John Doe t/02` or as `n/John Doe`. * Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. + e.g. `[t/TUTORIALS]…​` can be used as ` ` (i.e. 0 times), `t/03`, `t/11 t/20` 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. + e.g. if the command specifies `n/NAME m/MAJOR`, `m/MAJOR n/NAME` is also acceptable. -* 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`. +* 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 `list 123`, it will be interpreted as `list`. + +* Commands must be in lower case.
e.g. `list` is a valid command, but `LIST` is not. * If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-### Viewing help : `help` +> Back to Table of Contents +
+ +
+ +## General Commands + +
:exclamation: **Important:** +StudentConnect provides a set of general commands to make it more convenient for you to navigate the app. +
+ +
+ +### Viewing Help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page in the user guide and a button to copy the link.
+Provides a list of requirements for forming a group in CS2103T/CS2101. + +#### Format: `help` + +#### Expected Output: +* GUI: Help window opened with help message, copy button and requirement message. +* Message: `Opened help window.` ![help message](images/helpMessage.png) -Format: `help` +![help window](images/help.png) + +
+ +### Clearing All Entries : `clear` + +Clears all entries from the system upon confirmation in the pop-up. + +#### Format: `clear` +* Confirmation Pop-up opened. +* Message: `Opened confirmation window. Please ensure you use the exit command when exiting StudentConnect for successful reset.` + +#### Expected Output (Success): +* GUI: All students' details are removed from the student list.
+ +![result for 'clear' pop-up](images/clearPopUp.png) + +![result for 'clear' GUI](images/clearUI.png) + +#### Expected Output (Failure or Cancellation): +* Case: Clear command fails. +* Case: You press `cancel` on the confirmation pop-up.
+ GUI: All students' details remain on the student list. + +![result for cancelled 'clear' GUI](images/clearCancel.png) + +
+ +### Exiting The Program : `exit` +Exits the program. + +#### Format: `exit` + +#### Expected Output (Success): +* GUI: Application window closes. +* Message (before closing): `Thank you for using StudentConnect! Exiting the application now…` + +![Exit](images/exit.png) + +#### Expected Output (Failure): +* Message: `Error: Exiting the program failed.` + +> Back to Table of Contents +
+
+ +## Student Commands + +
:exclamation: **Important:** +StudentConnect provides a set of student information to make it more convenient for students to connect with others. +
-### Adding a person: `add` +
-Adds a person to the address book. +### Adding a Student : `add` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Adds a student to the system. + +#### Format: `add n/NAME m/MAJOR y/YEAR e/EMAIL d/DESCRIPTION [t/TUTORIALS]… [sm/SOCIALMEDIA]… nt/NATIONALITY g/GENDER` + +
:bulb: **Tip:** +Including social media links and tutorial groups are optional. +
:bulb: **Tip:** -A person can have any number of tags (including 0) +A student can include multiple tutorial groups they are interested in. Add multiple tutorial groups by using `t/` repeatedly.
-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` +
:bulb: **Tip:** +A student can have any number of social media links. Add multiple social media links by using `sm/` repeatedly. +
+ +#### Examples: +* `add n/John Doe m/Computer Science y/2 e/johnd@u.nus.edu d/I’m a Frontend Developer t/06 t/19 sm/https://www.linkedin.com/in/john-doe-123456789 nt/local g/m` +* `add n/Betsy Crowe m/Computer Science y/2 e/betsycrowe@u.nus.edu d/I’m adept at Backend technologies t/05 nt/foreigner g/f` + +#### Acceptable Values: +* Name: Full names with alphabetical characters. Maximum 30 characters. +* Major: Valid major names at NUS. View the valid majors list below for more information. +* Year: Numeric year level. Accept values between `1` and `6` inclusive. +* Email: Valid email address ending in `@u.nus.edu`. Maximum 20 characters. +* Description: Maximum 150 characters. +* Tutorials: Two digit integers between `01` and `22` inclusive. Multiple tutorial slots can be added by using `t/` repeatedly. +* Social Media Link: Valid URL format to social media account. It must start with `https://`. Multiple URLs can be added by using `sm/` repeatedly. +* Nationality: Either `local` or `foreigner`. +* Gender: A single character, either `m` or `f`. -### Listing all persons : `list` +#### Expected Output (Success): +* GUI: New student entry added in the main student list. +* Message: `Details added successfully! New student added: (details of new student).` -Shows a list of all persons in the address book. +![Add feature](images/add.png) -Format: `list` +Note: +* Social media links can be clicked. +* The link will be opened on a browser upon clicking. -### Editing a person : `edit` +![social_media](images/socialMediaLinks.png) +
Output with a single social media link
-Edits an existing person in the address book. +![social_media](images/multipleLinks.png) +
Output with multiple social media links
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +#### Expected Output (Failure): +* Case: Invalid command format, e.g. `add`, `add 1`, etc.
+Message: `Invalid command format! add: Adds a student to StudentConnect.`
+ `Parameters: n/NAME m/MAJOR y/YEAR e/EMAIL d/DESCRIPTION [t/TUTORIALS]... [sm/SOCIAL_MEDIA_LINK]... nt/NATIONALITY g/GENDER`
+ `Example: add n/John Doe m/Computer Science y/2 e/johnd@u.nus.edu d/I love programming in my free time`
+ `t/02 sm/https://www.linkedin.com/in/john-doe-123456789 nt/local g/M;` -* 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, …​ +
+ +### Listing All Students : `list` + +Shows a list of all students in the system. + +#### Format: `list` + +#### Expected Output (Success): +* GUI: List of all student entries in the system. +* Message: `Viewing all students` + +![sample result for 'list'](images/list.png) + +
+ +#### Expected Output (Failure): +* Message: `Error: Unable to retrieve student entries. Please try again.` + +
+ +### Editing a Student : `edit` + +Edits an existing student in the system. + +#### Format: `edit EMAIL [n/NAME] [m/MAJOR] [y/YEAR] [e/EMAIL] [d/DESCRIPTION] [t/TUTORIALS]…​ [sm/SOCIALMEDIA]…​ [nt/NATIONALITY] [g/GENDER]` + +* Edits the student with the specified EMAIL. * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* When editing tutorials, the existing tutorials of the student will be removed i.e. adding of tutorials is not cumulative. +* When editing social media, the existing social media of the student will be removed i.e. adding of social media is not cumulative. +* You can remove all the student's social media by typing `sm/` without specifying any social media after it. + +
:bulb: **Tip:** +A student can edit to include multiple tutorial groups they are interested in. Add multiple tutorial groups by using `t/` repeatedly. +
+ +
:bulb: **Tip:** +A student can edit to have any number of social media links. Add multiple social media links by using `sm/` repeatedly. +
-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. +#### Examples: +* `edit johnd@u.nus.edu y/3 e/johndoe@u.nus.edu` Edits the year and email address of the student with the email `johnd@u.nus.edu` to be `3` and `johndoe@u.nus.edu` respectively. +* `edit betsycrowe@u.nus.edu n/Betsy Crower sm/` Edits the name of the student with the email `betsycrowe@u.nus.edu` to be `Betsy Crower` and clears all existing social media. -### Locating persons by name: `find` +#### Acceptable Values: +* EMAIL: a previously registered email address ending in `@u.nus.edu`. -Finds persons whose names contain any of the given keywords. +#### Expected Output (Success): +* GUI: Student details updated in the student list. +* Message: `Details edited successfully! Edited Student: [Updated data]` + +![sample result for 'edit'](images/edit.png) -Format: `find KEYWORD [MORE_KEYWORDS]` +#### Expected Output (Failure): +* Case: No fields provided for edit.
+Message: `At least one field to edit must be provided.` +* Case: Email not found in the system.
+Message: `Student with the provided email not found.` -* 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` +
+ +### Finding a Student by Name : `find` + +Finds student(s) whose name(s) contain any of the given keywords. + +#### Format: `find KEYWORD [MORE_KEYWORDS]…​` + +* The search is case-insensitive. e.g. `john` will match `John`. +* The order of the keywords does not matter. e.g. `John Doe` will match `Doe John`. * 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` +* Partial words can be matched. e.g. `John` will match `Johnny`. +* Students matching at least one keyword will be returned (i.e. `OR` search). e.g. `John Crowe` will return `John Doe`, `Betsy Crowe`. -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +#### Examples: +* `find John` returns `john`, `John Doe` and `Johnny Wee`. +* `find john betsy` returns `John Doe`, `Betsy Crowe`.
-### Deleting a person : `delete` +#### Expected Output (Success): +* GUI: List of all student entries whose name(s) match the keyword(s) in the system. + +![result for 'find alex'](images/findAlexResult.png) -Deletes the specified person from the address book. +#### Expected Output (Failure): +* Case: Invalid command format, e.g. `find`.
+Message: `Invalid command format!`
+ `find: Finds all students whose names contain any of the specified keywords (case-insensitive) and displays them as a list with index numbers.`
+ `Parameters: KEYWORD [MORE_KEYWORDS]...`
+ `Example: find alice bob charlie` -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, …​ +### Filtering Students by Tutorial : `filter` -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. +Filters students by tutorial based on the given slots. -### Clearing all entries : `clear` +#### Format: `filter SLOT [MORE SLOTS]…​` -Clears all entries from the address book. +* The slots must be 2-digit numbers between `01` and `22` inclusive. +* Tutorials are only accepted as 2-digits, i.e. `3` is not a valid tutorial, but `03` is. +* The order of the slots does not matter. e.g. `08 15` will match `15 08`. +* Only the tutorial is searched. +* Students matching at least one tutorial slot will be returned (i.e. `OR` search). e.g. `03 12` will return `03 16`, `04 12`. -Format: `clear` +#### Examples: +* `filter 10` returns students in `T10`, `T06 T10` and `T10 T18`. +* `filter 12 16` returns students in `T05 T12`, `T16 T22`. -### Exiting the program : `exit` +#### Expected Output (Success): +* GUI: List of all student entries whose tutorial(s) match the slot(s) in the system. + +![result for 'filter 04'](images/filter.png) -Exits the program. +
-Format: `exit` +#### Expected Output (Failure): +* Case: Invalid command format, e.g. `filter`.
+ Message: `Invalid command format!`
+ `filter: Filters all students whose tutorials match any of the specified slots (2-digit numbers between 01 and 22) and displays them as a list with index numbers.`
+ `Parameters: SLOT [MORE_SLOTS]...`
+ `Example: filter 08 15` +* Case: Invalid slot(s) is provided, e.g. `filter 25`, `filter 0`, etc.
+ Message: `Tutorials should be 2-digit numbers between 01 and 22.` -### Saving the data +
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +### Deleting a Student : `delete` -### Editing the data file +Deletes a specific student and all personal details based on email. -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +#### Format: `delete EMAIL` -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +* Deletes the student with the specified `EMAIL`. +* The email must be registered in the system. + +#### Examples: +* `list` followed by `delete alexy@u.nus.edu` deletes Alex Yeo from the system. + +#### Acceptable Values: +* EMAIL: a previously registered email address ending in `u.nus.edu`. + +#### Expected Output (Success): +* GUI: Student details removed from student list. +* Message: `Student deleted successfully! [Deleted student's details]` + +![Delete feature](images/delete.png) + +#### Expected Output (Failure): +* Case: Provided email not registered in system.
+ Message: `Student with the provided email not found.` +* Case: Invalid command format e.g. `delete 02`.
+ Message: `Invalid command format!`
+ `delete: Deletes the student identified by the email address.`
+ `Parameters: EMAIL`
+ `Example: delete alexyeoh@u.nus.edu` + +> Back to Table of Contents +
+
+ +## Group Commands + +
:exclamation: **Important:** +StudentConnect provides a set of group formation features to make it more convenient for students to find group mates. A group consists of 3 main components: a group number, a tutorial number and the group members. +
**Group number:** Used to uniquely identify the group. +
**Tutorial number:** This number is set by the creator of the group and serves as an indication of which tutorial class the members of the group are interested in enrolling in. It **does not** restrict students who do not have a matching tutorial number in their profile from joining the group. Running the checkGroup command displays a warning if there are members whose tutorial numbers do not match the group's tutorial number. +
**Group members:** The students who are members of this group.
-### Archiving data files `[coming in v2.0]` +
+ +### Creating a New Group : `create` + +Creates a new empty group with the given tutorial number. The group number is automatically assigned and is used to uniquely identify each group. The tutorial number serves as an indication of which tutorial class the members of the group are interested in enrolling in. This can provide information about the tutorial preferences of the group members, to other students who are looking for a group to join. + +#### Format: `create t/TUTORIAL` + +#### Expected Output (Success): +* GUI: A new empty group, with a group number, is created. +* Message: `Group created successfully! Group number is [GROUP_NUMBER]` + +![Sample result for create](images/create.png) + +#### Expected Output (Failure): +* Case: Invalid command format, e.g. `create`, `create 02`, etc.
+Message: `Invalid command format!`
+ `create: Creates a new empty group.`
+ `Parameters: t/TUTORIAL Example: create t/02`
+* Case: Invalid tutorial number is provided, e.g. `create t/0`, `create t/25`, etc.
+ Message: `Tutorials should be 2-digit numbers between 01 and 22.` + +
+ +### Listing All Groups : `listGroup` + +Displays a list of all groups. For each group, the group number, and the names and emails of the members are shown. + +#### Format: `listGroup` + +#### Expected Output (Success): +* GUI: A list of all groups that are in the system is shown. +* Message: `Viewing all groups` + +![Sample result for listGroup](images/listGroup.png) + +#### Expected Output (Failure): +* Message: `Error: Unable to retrieve group entries. Please try again.` + +
+ +### Deleting a Group : `deleteGroup` + +Deletes a group from the system, based on group number. + +#### Format: `deleteGroup gr/GROUP_NUMBER` + +#### Examples: +* `deleteGroup gr/2` deletes Group 2 from the system. +* `deleteGroup gr/5` deletes Group 5 from the system. + +#### Acceptable Values: +* GROUP_NUMBER: Must be a non-zero unsigned integer. + +
+ +#### Expected Output (Success): +* GUI: Specified group is no longer visible. +* Message: `Group deleted successfully! Deleted Group: [GROUP_NUMBER]` + +![result for 'deleteGroup gr/3'](images/deleteGroup.png) + +#### Expected Output (Failure): +* Case: Group with specified number is not in the system.
+ Message: `Group with the provided group number not found.` + +
+ +### Joining a Group : `join` + +Adds a student to the specified group. + +#### Format: `join e/EMAIL gr/GROUP_NUMBER` + +#### Expected Output (Success): +* GUI: The student's name and email are displayed in the specified group's card. +* Message: `Join successful! [NAME] has joined Group [GROUP_NUMBER]!` + +#### Expected Output (Failure): +* Case: Email not found in the system.
+Message: `Student with the provided email not found.` +* Case: Group number not found in the system.
+Message: `Group with the provided group number not found.` +* Case: Student has been added in the group already.
+Message: `The provided student is already a member of the provided group.` +* Case: Group has 5 members and is full.
+Message: `Join failed as the group already has 5 members.` +* Case: Student is found in another group already.
+Message: `The provided student is already in another group.` + +![Sample result for join](images/join.png) + +
+ +### Leaving a Specific Group : `leave` + +Deletes a member from a specific group, indicating that they have left. + +#### Format: `leave e/EMAIL gr/GROUP_NUMBER` + +* Removes student from specified group. + +
+ +#### Examples: +* `leave e/johnd@u.nus.edu gr/1` Removes member with email `johnd@u.nus.edu` from Group 1. +* `leave e/bettyc@u.nus.edu gr/11` Removes member with email `bettyc@u.nus.edu` from Group 11. + +#### Acceptable Values: +* GROUP_NUMBER: Must be a non-zero unsigned integer. +* EMAIL: Must be a valid NUS email registered in the system. + +#### Expected Output (Success): +* GUI: Student details removed from specified group. +* Message: `Leave successful! NAME has left group 1!` + +![sample result for 'leave'](images/leave.png) + +#### Expected Output (Failure): +* Case: Email not found in the system.
+Message: `Person with the provided email not found.` +* Case: Group number not found in the system.
+Message: `Group with the provided group number not found.` +* Case: Student is not a member of the provided group.
+Message: `The above student is not a member of the provided group.` + +
+ +### Finding a Group by Group Number : `findGroup` + +Finds group(s) with group number(s) that matches any of the given keywords. + +#### Format: `findGroup KEYWORD [MORE_KEYWORDS]` + +* The order of the keywords does not matter. e.g. `5 12` will match `12 5`. +* Only the group number is searched. +* Only the full keywords will be matched. e.g. `1` will not match `12`. +* Groups matching one keyword will be returned (i.e. `OR` search). e.g. `5 12` will return `5`, `12`. +* The keyword(s) must be a non-zero unsigned integer. + +#### Examples: +* `findGroup 7` returns Group `7`. +* `findGroup 7 15` returns Group `7`, Group `15`. + +#### Expected Output (Success): +* GUI: List of all group entries whose group number(s) match the keyword(s) in the system. + +![sample result for 'findGroup'](images/findGroup.png) + +#### Expected Output (Failure): +* Case: Invalid command format is provided, e.g. `findGroup`.
+ Message: `Invalid command format!`
+ `findGroup: Finds all groups whose number contain any of the specified keywords and displays them as a list with index numbers.`
+ `Parameters: KEYWORD [MORE_KEYWORDS]...`
+ `Example: findGroup 1 5 10` +* Case: Invalid keyword(s) is provided, e.g. `findGroup a`, `findGroup 0`, etc.
+ Message: `Group number is not a non-zero unsigned integer.` + +
+ +### Filtering Groups by Tutorial : `filterGroup` + +Filters the groups by tutorial based on the given slot. + +#### Format: `filterGroup SLOT` + +* The slot must be 2-digit numbers between `01` and `22` inclusive. +* Tutorials are only accepted as 2-digits, i.e. `3` is not a valid tutorial, but `03` is. +* Only the tutorial is searched. + +
+ +#### Examples: +* `filterGroup 03` returns groups that belong to `T03`. + +#### Expected Output(Success): +* GUI: List of all group entries with the tutorial that match the slot in the system. + +![sample result for 'filterGroup'](images/filterGroup.png) -_Details coming soon ..._ +#### Expected Output (Failure): +* Case: Invalid command format is provided, e.g. `filterGroup`.
+Message: `Invalid command format!`
+ `filterGroup: Filters all groups that belong to the specified tutorial slot (2-digit numbers between 01 and 22) and displays them as a list with index numbers.`
+ `Parameters: SLOT`
+ `Example: filterGroup 01"` +* Case: Invalid slot is provided, e.g. `filterGroup 0`, `filterGroup 25`, etc.
+Message: `Tutorials should be 2-digit numbers between 01 and 22.` + +
+ +
+ +### Checking a Group : `checkGroup` + +Checks if a group fulfils the group requirements of the course. + +#### Format: `checkGroup GR0UP_NUMBER` + +* Checks the group with the specified `GROUP_NUMBER`. +* The group number must come from a group that has been created in the system. +* checkGroup does not restrict students from joining a group, instead, it provides helpful alerts to help groups adhere to the criteria set by CS2103T and CS2101. + +#### Examples: +* `checkGroup 4` checks the group with a group number `4` if it is created in the system. + +#### Expected Output (Success): +* Case: Group fulfils the group requirements.
+Message: `Group GROUP_NUMBER`
+ `Group fulfils the diversity requirements of CS2103T.` + +![sample result for 'checkGroup'](images/checkGroup.png) + +* Case: Group has no members.
+Message: `Group GROUP_NUMBER`
+ `Group does not have any members.`
+ `You can enter the help command for more information on group requirements.` +* Case: Group has only 1 member.
+Message: `Group GROUP_NUMBER`
+ `Group has only one member.`
+ `You can enter the help command for more information on group requirements.` +* Case: Group has more than 1 member and does not fulfil the group requirements. Possible messages include:
+Message: `Group GROUP_NUMBER`
+Message: `Group has less than 5 members.`
+Message: `Group size has exceeded limit with more than 5 members.`
+Message: `Group comprises of members of the same nationality.`
+Message: `Group comprises of members of the same gender.`
+Message: `Not every group member's tutorial matches the group's tutorial.`
+Message: `You can enter the help command for more information on group requirements.`
+ +#### Expected Output (Failure): +* Case: Invalid command format is provided, e.g. `checkGroup`.
+Message: `Invalid command format!`
+ `checkGroup: Checks the group identified by its group number.`
+ `Parameters: GROUP_NUMBER"`
+ `Example: checkGroup 1` +* Case: Group not found in the system.
+Message: `Group with the provided group number not found.` + +
+ +### Listing All Tasks : `tasks` + +Lists out all tasks for a specific group. + +#### Format: `tasks GROUP_NUMBER` + +* Lists out all tasks for the specified group. + +#### Examples: +* `tasks 2` Lists out all tasks for group `2`. +* `tasks 5` Lists out all tasks for group `5`. + +#### Acceptable Values: +* GROUP_NUMBER: Must be a non-zero unsigned integer. + +
+ +#### Expected Output (Success): +* GUI: List of all tasks for the specified group is displayed, the specified group and group members are shown. +* Message: `Here are the tasks for group [GROUP_NUMBER]: [list of tasks]` + +![sample result for 'tasks'](images/tasks.png) + +#### Expected Output (Failure): +* Case: Invalid tasks command, e.g. `tasks gr/3`, `tasks t`, etc.
+ Message: `Invalid command format!`
+ `tasks: Lists out all tasks for a specific group.`
+ `Parameters: GROUP_NUMBER`
+ `Example: tasks 3` +* Case: Invalid group number as the group has not yet been created.
+ Message: `Group with the provided group number not found.` + +
+ +
+ +### Marking a Task as Done : `mark` + +Mark a task for a specified group as done. + +#### Format: `mark gr/GROUP_NUMBER ti/TASK_INDEX` + +* Marks the task of the specified group as done. + +#### Examples: +* `mark gr/2 ti/1` Marks task `1` of group `2` as done. +* `mark ti/2 gr/12` Marks task `2` of group `12` as done. + +#### Acceptable Values: +* GROUP_NUMBER: Must be an integer value that is grater than 0 and a group number that is found in the group list. +* TASK_INDEX: Must be an integer value that is greater than 0 and smaller than task size. + +#### Expected Output (Success): +* GUI: The task in the specified group is marked as done and task list is updated. +* Message: `Marked task number (ti) for group (gr) [and displays the updated task list]` + +![sample result for 'mark'](images/mark.png) + +#### Expected Output (Failure): +* Case: Invalid mark command, e.g. `mark`, `mark t`, etc.
+Message: `Invalid command format! mark: Mark task specified as done. Parameters: gr/GROUP_NUMBER ti/TASK_INDEX Example: mark gr/2 ti/3` +* Case: Invalid group number, a group number that is not found from the list in `listGroup` command.
+Message: `Group with the provided group number not found.` +* Case: Invalid group number, e.g. zero or negative numbers.
+Message: `Group number is not a non-zero unsigned integer.` +* Case: Invalid task index, e.g. zero or negative numbers.
+Message: `Task index must be a positive integer.` +* Case: Invalid task index, e.g. task index greater than the number of tasks found from the list in `tasks` command.
+Message: `Invalid task index. Task not found.` + +
+ +
+ +### Marking a Task as Not Done : `unmark` + +Mark a task for a specified group as not done. + +#### Format: `unmark gr/GROUP_NUMBER ti/TASK_INDEX` + +* Marks the task of the specified group as not done. + +#### Examples: +* `unmark gr/7 ti/3` Marks task `3` of group `7` as not done. +* `unmark ti/5 gr/9` Marks task `5` of group `9` as not done. + +#### Acceptable Values: +* GROUP_NUMBER: Must be an integer value that is grater than 0 and a group number that is found in the group list. +* TASK_INDEX: Must be an integer value that is greater than 0 and smaller than task size. + +#### Expected Output (Success): +* GUI: The task in the specified group is marked as not done and task list is updated. +* Message: `Unmarked task number (ti) for group (gr) [and displays the updated task list]` + +![sample result for 'unmark'](images/unmark.png) + +#### Expected Output (Failure): +* Case: Invalid unmark command, e.g. `unmark`, `unmark t`, etc.
+ Message: `Invalid command format! unmark: Mark task specified as not done. Parameters: gr/GROUP_NUMBER ti/TASK_INDEX Example: unmark gr/2 ti/3` +* Case: Invalid group number, a group number that is not found from the list in `listGroup` command.
+Message: `Group with the provided group number not found.` +* Case: Invalid group number, e.g. zero or negative numbers.
+Message: `Group number is not a non-zero unsigned integer.` +* Case: Invalid task index, e.g. zero or negative numbers.
+Message: `Task index must be a positive integer.` +* Case: Invalid task index, e.g. task index greater than the number of tasks found from the list in `tasks` command.
+Message: `Invalid task index. Task not found.` + +> Back to Table of Contents +
+
+ +### Saving The Data + +Students' data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. -------------------------------------------------------------------------------------------------------------------- +### Editing The Data File + +Students' data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. + +
:exclamation: **Caution:** +If your changes to the data file makes its format invalid, StudentConnect will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. +
+ +> Back to 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 StudentConnect home folder. --------------------------------------------------------------------------------------------------------------------- +**Q**: Where is my data being saved?
+**A**: It is saved in `[JAR file location]/data/addressbook.json`. +> Back to Table of Contents +
+
-## Known issues +## Known Issues 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. +2. **Clear Command**, if you use the clear command, but exit the application incorrectly using the red exit button instead of running the exit command, data will not be cleared. Kindly always use the exit command to leave the application. +> Back to Table of Contents +
+
+ +## Command Summary + +| Action | Format, Examples | +|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Help** | `help` | +| **Clear** | `clear` | +| **Exit** | `exit` | +| **Add** | `add n/NAME m/MAJOR y/YEAR e/EMAIL d/DESCRIPTION [t/TUTORIALS]…​ [sm/SOCIALMEDIA]…​ nt/NATIONALITY g/GENDER`
e.g., `add n/Betsy Crowe m/Computer Science y/2 e/betsycrowe@u.nus.edu t/05 d/I’m adept at Backend technologies nt/local g/f` | +| **List** | `list` | +| **Edit** | `edit EMAIL [n/NAME] [m/MAJOR] [y/YEAR] [e/EMAIL] [d/DESCRIPTION] [t/TUTORIALS]…​ [sm/SOCIALMEDIA]…​ [nt/NATIONALITY] [g/GENDER]`
e.g.,`edit jameslee@u.nus.edu n/James Lee e/jameslee@u.nus.edu` | +| **Find** | `find KEYWORD [MORE_KEYWORDS]…​`
e.g., `find James Jake` | +| **Filter** | `filter SLOT [MORE_SLOTS]…​`
e.g., `filter 05 11` | +| **Delete** | `delete EMAIL`
e.g., `delete betsycrowe@u.nus.edu` | +| **Create group** | `create t/[TUTORIAL]`
e.g., `create t/01` | +| **List groups** | `listGroup` | +| **Delete group** | `deleteGroup gr/[GROUP_NUMBER}`
e.g., `deleteGroup gr/1` | +| **Join group** | `join e/[EMAIL] gr/[GROUP_NUMBER]`
e.g., `join e/johnd@u.nus.edu gr/1` | +| **Leave group** | `leave e/[EMAIL] gr/[GROUP_NUMBER}`
e.g., `leave e/johnd@u.nus.edu gr/1` | +| **Find group** | `findGroup KEYWORD [MORE_KEYWORDS]`
e.g., `findGroup 7 15` | +| **Filter group** | `filter SLOT`
e.g., `filterGroup 3` | +| **Check group** | `checkGroup GR0UP_NUMBER`
e.g., `checkGroup 4` | +| **List Tasks** | `tasks GROUP_NUMBER`
e.g., `tasks 5` | +| **Mark** | `mark gr/GROUP_NUMBER ti/TASK_INDEX`
e.g., `mark gr/2 ti/1` | +| **Unmark** | `unmark gr/GROUP_NUMBER ti/TASK_INDEX`
e.g., `unmark gr/5 ti/1` | + +> Back to Table of Contents +
+
+ +## Appendix +This section includes details on the requirements of each field in the StudentConnect system. + +### Name Format +The name field indicates the student's name. --------------------------------------------------------------------------------------------------------------------- -## 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` +
:bulb: **Tip:** +Ensure that the name is appropriately formatted with only letters and spaces, without exceeding the specified character limit below. Special characters and digits are not permitted. +
+ +- **Type:** Alphabetic. +- **Character Restrictions:** Only alphabetical characters (A-Z, a-z) and spaces are allowed. +- **Length:** Must not exceed 30 characters. +- **Blank Fields:** The field should not be left blank. + +**Examples of Valid Inputs:** +- John Doe +- Alice +- La Niña +- Élise DuPont + +**Examples of Invalid Inputs:** +- John123 (Invalid due to numeric characters) +- John_Doe (Invalid due to underscore) +- [Blank Field] (Invalid as the field cannot be blank) +- Elizabeth Alexandra Mary Windsor (Invalid as it exceeds 30 characters) + +
+
+ +### Valid Majors +The majors field indicates the major that the student is enrolled in. + +
:bulb: **Tip:** +If your major is not in the list below, you can simply write `Others` as your major. +
+ +
:bulb: **Tip:** +If you have a double degree, double major or a major-minor, you are required to choose only one major. Please select any one of the majors listed below. +
+ +For now, we accept the following majors in National University of Singapore(NUS): + +| Major | +|-------------------------------------------| +| Accountancy | +| Actuarial Studies | +| Anthropology | +| Architecture | +| Biological Sciences | +| Biomedical Engineering | +| Business Administration | +| Business Administration (Accountancy) | +| Business Analytics | +| Chemical Engineering | +| Chemistry | +| Chinese Language | +| Chinese Studies | +| Communications and New Media | +| Civil Engineering | +| Computer Engineering | +| Computer Science | +| Data Science and Analytics | +| Data Science and Economics | +| Dentistry | +| Economics | +| Electrical Engineering | +| English Language | +| English Literature | +| Environmental Engineering | +| Environmental Studies | +| Food Science and Technology | +| Geography | +| Global Studies | +| History | +| Japanese Studies | +| Industrial Design | +| Industrial Engineering | +| Industrial and Systems Engineering | +| Information Systems | +| Information Security | +| Infrastructure and Project Management | +| Landscape Architecture | +| Law | +| Life Sciences | +| Malay Studies | +| Management | +| Marketing | +| Materials Science and Engineering | +| Mathematics | +| Mechanical Engineering | +| Medicine | +| Pharmaceutical Science | +| Philosophy | +| Philosophy, Politics, and Economics | +| Physics | +| Political Science | +| Psychology | +| Quantitative Finance | +| Real Estate | +| Social Work | +| Sociology | +| South Asian Studies | +| Southeast Asian Studies | +| Statistics | +| Systems Engineering | +| Theatre Studies | +| Urban Studies | +| Visual Communications | + +
+
+ +### Valid Years +The year field is used to represent the student's current year of study. + +
:bulb: **Tip:** +This field strictly requires a single-digit number. Ensure there are no leading zeros or non-numeric characters. +
+ +| Year | +|------| +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | +| 6 | + + +
+ +### Email Formats +The email field is used to represent the student's email, and is the unique identifier for each student. + +
:bulb: **Tip:** +Ensure the email address includes the specified domain and the local-part conforms to the outlined character restrictions and length below. +
+ +1. **Local-Part Format:** + - **Allowed Characters:** Alphanumeric characters (A-Z, a-z, 0-9) and special characters (+, _, ., -) only. + - **Character Limit:** Must not exceed 20 characters. + - **Positioning of Special Characters:** The local-part must not start or end with any of the special characters (+, _, ., -). + - **Structure:** It should be in the format of `local-part@u.nus.edu`. + +2. **Domain Name:** + - **Fixed Domain:** The domain name must be `u.nus.edu`. + - **Symbol:** The local-part and domain name should be separated by an '@' symbol. + +**Examples of Valid Inputs:** +- johndoe@u.nus.edu +- alice.bob@u.nus.edu +- n_user123@u.nus.edu + +**Examples of Invalid Inputs:** +- john@u.nus.edu (Invalid as it exceeds 20 characters in the local-part) +- .johndoe@u.nus.edu (Invalid as the local-part starts with a special character) +- johndoe@example.com (Invalid as the domain name is not 'u.nus.edu') +- johndoe@u.nus (Invalid as the domain name is incomplete) + +
+ +### Description Formats +The description field is to allow student's to put any fun facts or information they'd like to share. + +
:bulb: **Tip:** +Descriptions that are blank or that exceed 150 characters are not accepted. +
+ +**Length Constraints:** +- **Minimum Length:** The description must not be blank. +- **Maximum Length:** The description should not exceed 150 characters. + +**Examples of Valid Inputs:** +- "Minoring in Mathematics" +- "Backend Developer" +- "In need of two more members!" + +
+
+ +### Valid Tutorials +The tutorial field is used to represent tutorial slots students' are interested in or have been assigned. + +
:bulb: **Tip:** +This field strictly requires a double-digit number. Ensure are no non-numeric characters.
+
+ +| Tutorial | +|----------| +| 01 | +| 02 | +| 03 | +| 04 | +| 05 | +| 06 | +| 07 | +| 08 | +| 09 | +| 10 | +| 11 | +| 12 | +| 13 | +| 14 | +| 15 | +| 16 | +| 17 | +| 18 | +| 19 | +| 20 | +| 21 | +| 22 | + +
+ +### Valid Social Media Links +The social media links field is used for entering URLs that direct to social media profiles or pages. + +
:bulb: **Tip:** + Ensure that the social media links conform to the standard URL format, with the correct protocol and a valid domain name. The link should be fully functional and direct to the intended social media page.
+ +- **Protocol Prefix:** Must begin with either "http://" or "https://". +- **Domain Name:** + - **Allowed Characters:** Alphanumeric characters (A-Z, a-z, 0-9), dots (.), and hyphens (-). + - **Requirement:** The domain name must consist of one or more of the allowed characters. +- **Structure:** The format should follow the standard URL structure, starting with the protocol prefix, followed by the domain name. + +**Examples of Valid Inputs:** +- http://facebook.com/username +- https://twitter.com/username +- http://www.linkedin.com/in/username + +**Examples of Invalid Inputs:** +- www.instagram.com/username (Invalid as it lacks the "http://" or "https://" prefix) +- https://facebook_com/username (Invalid due to the use of an underscore in the domain name) +- https:/twitter.com/username (Invalid due to incorrect protocol format) + +
+ +### Valid Nationalities +Nationality field is used to display the nationality of the student. +
:bulb: **Tip:** +The nationality field is case-insensitive, but can only contain values `local` or `foreigner`. +
+ +| Nationality | +|-------------| +| local | +| foreigner | + +
+ +### Valid Genders +Gender field is used to display the gender of the student. +
:bulb: **Tip:** +The gender field is case-insensitive, but can only contain values `M` or `F`. +
+ +| Gender | +|--------| +| M | +| F | + +
+ +### Valid Group Numbers +The group number field is used to specify the numeric identifier of a group. + +
:bulb: **Tip:** +Verify that the group number entered is for a group that exists in the system.
+Ensure the number is purely numeric without any alphabetic characters or special symbols. +
+ +- **Type:** Numeric. +- **Reference Requirement:** The number must correspond to a group that has already been created within the system. +- **Uniqueness:** Each group number is unique. It should not duplicate the identifier of another group. + +
+ +### Valid Task Indexes +The task index field is used to differentiate tasks. + +
:bulb: **Tip:** +Verify that the task index entered is for a task that exists for that group.
+Ensure the number is purely numeric without any alphabetic characters or special symbols. +
+ +- **Type:** Numeric +- **Reference Requirement:** The number must correspond to a task within the system. +- **Uniqueness:** Each task number is unique. It should not duplicate the identifier of another task . + +> Back to Table of Contents +
+
+ +## Glossary + +This glossary is intended to provide definitions for terms that may be unfamiliar to you. It is arranged in alphabetical order. + +- **CS2101**: Effective Communication for Computing Professionals - An NUS course designed to equip computing professionals with essential communication skills, both in technical and non-technical contexts. It covers the creation of clear and comprehensible software documentation and effective communication strategies for diverse audiences. + +- **CS2103T**: Software Engineering - An NUS course focusing on the systematic and rigorous development of software systems. It covers essential concepts and analytical tools necessary for software engineering. + +- **Case-Insensitive**: Refers to the handling of text where uppercase and lowercase letters are treated as equivalent. For instance, in a case-insensitive search, searching for "Java" or "java" would yield the same results. + +- **Command Terminal**: An interface in a computing environment where you can input text commands to perform specific tasks. Some examples include "Terminal" on macOS and "Powershell" on Windows. + +- **Double Degree**: An academic program where a student earns two distinct degrees simultaneously, usually in different fields. + +- **Double Major**: A type of academic degree where a student completes two sets of major requirements, although they receive just one degree. + +- **Major-Minor**: An academic program where a student completes a major (primary focus) and a minor (secondary concentration) in different subjects. + +- **Extraneous Parameters**: These are additional or unnecessary parameters given in a command that do not affect its execution but are not required for the command to function properly. + +- **GUI (Graphical User Interface)**: A type of user interface that allows you to interact with electronic devices through graphical icons and visual indicators, as opposed to text-based interfaces, typed command labels, or text navigation. + +- **Home Folder**: In computing, this is a personal directory assigned to you in a file system, where they store personal files, settings, and configurations. + +- **Issue (GitHub)**: A feature in GitHub used to track ideas, enhancements, tasks, or bugs for work on GitHub projects. + +- **Java 11**: A version of Java, a widely used programming language and computing platform. Java 11 includes various updates and features different from its predecessors. + +> Back to Table of Contents diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..e68586c9455 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "StudentConnect" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "nus-cs2103-AY2324S1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_includes/header.html b/docs/_includes/header.html index 33badcd4f99..dbaeffe0f8e 100644 --- a/docs/_includes/header.html +++ b/docs/_includes/header.html @@ -25,7 +25,7 @@ {%- endif -%} {%- endfor -%} {%- if site.repository -%} - + View on GitHub {%- endif -%} diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..49eb3e95305 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,8 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "StudentConnect"; font-size: 32px; } } - diff --git a/docs/_sass/minima/skins/classic.scss b/docs/_sass/minima/skins/classic.scss index 37ea9c5244c..a441ff0e667 100644 --- a/docs/_sass/minima/skins/classic.scss +++ b/docs/_sass/minima/skins/classic.scss @@ -8,7 +8,7 @@ $text-color: #111 !default; $background-color: #fdfdfd !default; $code-background-color: #eef !default; -$link-base-color: #2a7ae2 !default; +$link-base-color: #111754 !default; $link-visited-color: darken($link-base-color, 15%) !default; $table-text-color: lighten($text-color, 18%) !default; diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..263af490c3b 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,10 +8,10 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete johnd@u.nus.edu" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete johnd@u.nus.edu") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) diff --git a/docs/diagrams/CreateSequenceDiagram.puml b/docs/diagrams/CreateSequenceDiagram.puml new file mode 100644 index 00000000000..4c6f2e57a46 --- /dev/null +++ b/docs/diagrams/CreateSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic UI_COLOR_T2 +participant ":LogicManager" as LogicManager MODEL_COLOR +participant ":AddressBookParser" as AddressBookParser MODEL_COLOR +participant ":CreateCommandParser" as CreateCommandParser MODEL_COLOR +participant "d:CreateCommand" as CreateCommand MODEL_COLOR +participant ":CommandResult" as CommandResult MODEL_COLOR +end box + +box Model LOGIC_COLOR_T2 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("create t/01") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("create t/01") +activate AddressBookParser + +create CreateCommandParser +AddressBookParser -> CreateCommandParser +activate CreateCommandParser + +CreateCommandParser --> AddressBookParser +deactivate CreateCommandParser + +AddressBookParser -> CreateCommandParser : parse("t/01") +activate CreateCommandParser + +create CreateCommand +CreateCommandParser -> CreateCommand +activate CreateCommand + +CreateCommand --> CreateCommandParser : c +deactivate CreateCommand + +CreateCommandParser --> AddressBookParser : c +deactivate CreateCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CreateCommandParser -[hidden]-> AddressBookParser +destroy CreateCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CreateCommand : execute() +activate CreateCommand + +CreateCommand -> CreateCommand : generateGroupNumber() + +CreateCommand -> Model : addGroup(newGroup) +activate Model + +Model --> CreateCommand +deactivate Model + +create CommandResult +CreateCommand -> CommandResult +activate CommandResult + +CommandResult --> CreateCommand +deactivate CommandResult + +CreateCommand --> LogicManager : result +deactivate CreateCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 40ea6c9dc4c..329fba4985a 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -14,10 +14,10 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete johnd@u.nus.edu") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete johnd@u.nus.edu") activate AddressBookParser create DeleteCommandParser @@ -27,7 +27,7 @@ activate DeleteCommandParser DeleteCommandParser --> AddressBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +AddressBookParser -> DeleteCommandParser : parse("johnd@u.nus.edu") activate DeleteCommandParser create DeleteCommand @@ -49,7 +49,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deletePerson(johnd@u.nus.edu) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/GroupModelClassDiagram.puml b/docs/diagrams/GroupModelClassDiagram.puml new file mode 100644 index 00000000000..58729428a28 --- /dev/null +++ b/docs/diagrams/GroupModelClassDiagram.puml @@ -0,0 +1,49 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model as ModelPackage <>{ +Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs +Class "<>\nModel" as Model +Class AddressBook +Class ModelManager +Class UserPrefs + +Class Person + +Class UniqueGroupList +Class Group { +- int number +} +Class Tutorial +Class TaskList + +Class I #FFFFFF +} +Class HiddenOutside #FFFFFF +HiddenOutside ..> Model + +AddressBook .up.|> ReadOnlyAddressBook + +ModelManager .up.|> Model +Model .right.> ReadOnlyUserPrefs +Model .left.> ReadOnlyAddressBook +ModelManager -left-> "1" AddressBook +ModelManager -right-> "1" UserPrefs +UserPrefs .up.|> ReadOnlyUserPrefs + +AddressBook *--> "1" UniqueGroupList +UniqueGroupList --> "~* all" Group +Group *--> "1" Tutorial +Group *-->"members *" Person +Group *--> "1" TaskList + +Group -[hidden]up--> I +UniqueGroupList -[hidden]right-> I + +ModelManager --> "~* filtered" Group + +@enduml diff --git a/docs/diagrams/Person.puml b/docs/diagrams/Person.puml new file mode 100644 index 00000000000..061a52e3706 --- /dev/null +++ b/docs/diagrams/Person.puml @@ -0,0 +1,27 @@ +@startuml + +class Person { + +Person(Name, Major, Year, Email, Description, Set, Set, Nationality, Gender) +} + +class Name +class Email +class Major +class Year +class Description +class Tutorial +class SocialMediaLink +class Nationality +class Gender + +Person --|> Name +Person --|> Email +Person --|> Major +Person --|> Year +Person --|> Description +Person --|> Tutorial +Person --|> SocialMediaLink +Person --|> Nationality +Person --|> Gender + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/PersonModelClassDiagram.puml similarity index 78% rename from docs/diagrams/ModelClassDiagram.puml rename to docs/diagrams/PersonModelClassDiagram.puml index 0de5673070d..003c0d76400 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/PersonModelClassDiagram.puml @@ -14,11 +14,15 @@ Class UserPrefs Class UniquePersonList Class Person -Class Address Class Email Class Name -Class Phone -Class Tag +Class Major +Class Year +Class Description +Class SocialMediaLink +Class Tutorial +Class Nationality +Class Gender Class I #FFFFFF } @@ -38,17 +42,18 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person Person *--> Name -Person *--> Phone Person *--> Email -Person *--> Address -Person *--> "*" Tag +Person *--> Major +Person *--> Year +Person *--> Description +Person *--> "*" SocialMediaLink +Person *--> "*" Tutorial +Person *--> Nationality +Person *--> Gender Person -[hidden]up--> I UniquePersonList -[hidden]right-> I -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email - ModelManager --> "~* filtered" Person + @enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..a62f82615d1 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,10 @@ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedGroup +Class JsonAdaptedSocialMedia +Class JsonAdaptedTutorial +Class JsonAdaptedTask } } @@ -38,6 +41,9 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedGroup +JsonAdaptedPerson --> "*" JsonAdaptedSocialMedia +JsonAdaptedPerson --> "*" JsonAdaptedTutorial +JsonAdaptedGroup --> "*" JsonAdaptedTask @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..9a3def22fdb 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -13,8 +13,11 @@ Class HelpWindow Class ResultDisplay Class PersonListPanel Class PersonCard +Class GroupListPanel +Class GroupCard Class StatusBarFooter Class CommandBox +Class ConfirmationPopup } package Model <> { @@ -31,12 +34,15 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox +MainWindow *-down-> "0..1" ConfirmationPopup MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "0..1" PersonListPanel +MainWindow *-down-> "0..1" GroupListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +GroupListPanel -down--> "*" GroupCard MainWindow -left-|> UiPart @@ -44,17 +50,24 @@ ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart +GroupListPanel --|> UiPart +GroupCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +ConfirmationPopup --|> UiPart PersonCard ..> Model +GroupCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic +GroupListPanel -[hidden]left- PersonListPanel PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox +HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter +StatusBarFooter -[hidden]left- ConfirmationPopup MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..c82aa2c350f 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/CreateSequenceDiagram.png b/docs/images/CreateSequenceDiagram.png new file mode 100644 index 00000000000..f0409c99b7a Binary files /dev/null and b/docs/images/CreateSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index e186f7ba096..505076dd12a 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/GroupListUI.png b/docs/images/GroupListUI.png new file mode 100644 index 00000000000..c54906f4d95 Binary files /dev/null and b/docs/images/GroupListUI.png differ diff --git a/docs/images/GroupModelClassDiagram.png b/docs/images/GroupModelClassDiagram.png new file mode 100644 index 00000000000..262b696c8ec Binary files /dev/null and b/docs/images/GroupModelClassDiagram.png differ diff --git a/docs/images/PersonClassDiagram.png b/docs/images/PersonClassDiagram.png new file mode 100644 index 00000000000..56b6aec1953 Binary files /dev/null and b/docs/images/PersonClassDiagram.png differ diff --git a/docs/images/PersonModelClassDiagram.png b/docs/images/PersonModelClassDiagram.png new file mode 100644 index 00000000000..63042e0755e Binary files /dev/null and b/docs/images/PersonModelClassDiagram.png differ diff --git a/docs/images/StorageClassDiagramNew.png b/docs/images/StorageClassDiagramNew.png new file mode 100644 index 00000000000..4c155069197 Binary files /dev/null and b/docs/images/StorageClassDiagramNew.png differ diff --git a/docs/images/StudentConnectLogo.png b/docs/images/StudentConnectLogo.png new file mode 100644 index 00000000000..a6ba0b2d38f Binary files /dev/null and b/docs/images/StudentConnectLogo.png differ diff --git a/docs/images/StudentListUI.png b/docs/images/StudentListUI.png new file mode 100644 index 00000000000..8c329fca38f Binary files /dev/null and b/docs/images/StudentListUI.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..d7b636490d3 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..4d6b229a1cf 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/add.png b/docs/images/add.png new file mode 100644 index 00000000000..a4bf65f9608 Binary files /dev/null and b/docs/images/add.png differ diff --git a/docs/images/bearypop.png b/docs/images/bearypop.png new file mode 100644 index 00000000000..09f14c853d9 Binary files /dev/null and b/docs/images/bearypop.png differ diff --git a/docs/images/checkGroup.png b/docs/images/checkGroup.png new file mode 100644 index 00000000000..bcc6c17d61e Binary files /dev/null and b/docs/images/checkGroup.png differ diff --git a/docs/images/clear.png b/docs/images/clear.png new file mode 100644 index 00000000000..0b5443f2480 Binary files /dev/null and b/docs/images/clear.png differ diff --git a/docs/images/clearCancel.png b/docs/images/clearCancel.png new file mode 100644 index 00000000000..2b8d5593382 Binary files /dev/null and b/docs/images/clearCancel.png differ diff --git a/docs/images/clearPopUp.png b/docs/images/clearPopUp.png new file mode 100644 index 00000000000..571940de972 Binary files /dev/null and b/docs/images/clearPopUp.png differ diff --git a/docs/images/clearUI.png b/docs/images/clearUI.png new file mode 100644 index 00000000000..68e76daf768 Binary files /dev/null and b/docs/images/clearUI.png differ diff --git a/docs/images/create.png b/docs/images/create.png new file mode 100644 index 00000000000..55ae81d3664 Binary files /dev/null and b/docs/images/create.png differ diff --git a/docs/images/delete.png b/docs/images/delete.png new file mode 100644 index 00000000000..b9304c5b2fb Binary files /dev/null and b/docs/images/delete.png differ diff --git a/docs/images/deleteGroup.png b/docs/images/deleteGroup.png new file mode 100644 index 00000000000..7f43516dbb3 Binary files /dev/null and b/docs/images/deleteGroup.png differ diff --git a/docs/images/desktop.ini b/docs/images/desktop.ini new file mode 100644 index 00000000000..79f04ac40e7 --- /dev/null +++ b/docs/images/desktop.ini @@ -0,0 +1,2 @@ +[LocalizedFileNames] +Ui.png=@Ui,0 diff --git a/docs/images/edit.png b/docs/images/edit.png new file mode 100644 index 00000000000..9748bfc6b5f Binary files /dev/null and b/docs/images/edit.png differ diff --git a/docs/images/exit.png b/docs/images/exit.png new file mode 100644 index 00000000000..777cf425a6a Binary files /dev/null and b/docs/images/exit.png differ diff --git a/docs/images/filter.png b/docs/images/filter.png new file mode 100644 index 00000000000..7f3aea5bd2c Binary files /dev/null and b/docs/images/filter.png differ diff --git a/docs/images/filterGroup.png b/docs/images/filterGroup.png new file mode 100644 index 00000000000..b21b77ec971 Binary files /dev/null and b/docs/images/filterGroup.png differ diff --git a/docs/images/find.png b/docs/images/find.png new file mode 100644 index 00000000000..6ff9007d574 Binary files /dev/null and b/docs/images/find.png differ diff --git a/docs/images/findAlexResult.png b/docs/images/findAlexResult.png new file mode 100644 index 00000000000..a7a96a42953 Binary files /dev/null and b/docs/images/findAlexResult.png differ diff --git a/docs/images/findGroup.png b/docs/images/findGroup.png new file mode 100644 index 00000000000..46336ce8236 Binary files /dev/null and b/docs/images/findGroup.png differ diff --git a/docs/images/help.png b/docs/images/help.png new file mode 100644 index 00000000000..692698b8551 Binary files /dev/null and b/docs/images/help.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..3127050796c 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/helpWindow.png b/docs/images/helpWindow.png new file mode 100644 index 00000000000..8c6a9f0e4ac Binary files /dev/null and b/docs/images/helpWindow.png differ diff --git a/docs/images/join.png b/docs/images/join.png new file mode 100644 index 00000000000..eeb70944fb8 Binary files /dev/null and b/docs/images/join.png differ diff --git a/docs/images/leave.png b/docs/images/leave.png new file mode 100644 index 00000000000..1111243be78 Binary files /dev/null and b/docs/images/leave.png differ diff --git a/docs/images/list.png b/docs/images/list.png new file mode 100644 index 00000000000..3eefcd1b728 Binary files /dev/null and b/docs/images/list.png differ diff --git a/docs/images/listGroup.png b/docs/images/listGroup.png new file mode 100644 index 00000000000..241885415ba Binary files /dev/null and b/docs/images/listGroup.png differ diff --git a/docs/images/maj0-0.png b/docs/images/maj0-0.png new file mode 100644 index 00000000000..ed0a06c263e Binary files /dev/null and b/docs/images/maj0-0.png differ diff --git a/docs/images/mark.png b/docs/images/mark.png new file mode 100644 index 00000000000..87c7655c72c Binary files /dev/null and b/docs/images/mark.png differ diff --git a/docs/images/multipleLinks.png b/docs/images/multipleLinks.png new file mode 100644 index 00000000000..c04989392c6 Binary files /dev/null and b/docs/images/multipleLinks.png differ diff --git a/docs/images/neyapraveen.png b/docs/images/neyapraveen.png new file mode 100644 index 00000000000..138d587afb5 Binary files /dev/null and b/docs/images/neyapraveen.png differ diff --git a/docs/images/pearlynnt.png b/docs/images/pearlynnt.png new file mode 100644 index 00000000000..34b634c3baa Binary files /dev/null and b/docs/images/pearlynnt.png differ diff --git a/docs/images/socialMediaLinks.png b/docs/images/socialMediaLinks.png new file mode 100644 index 00000000000..c2014604efe Binary files /dev/null and b/docs/images/socialMediaLinks.png differ diff --git a/docs/images/tasks.png b/docs/images/tasks.png new file mode 100644 index 00000000000..ae2320d1d89 Binary files /dev/null and b/docs/images/tasks.png differ diff --git a/docs/images/unmark.png b/docs/images/unmark.png new file mode 100644 index 00000000000..4f357187db4 Binary files /dev/null and b/docs/images/unmark.png differ diff --git a/docs/images/wnchan.png b/docs/images/wnchan.png new file mode 100644 index 00000000000..8f07e5ff775 Binary files /dev/null and b/docs/images/wnchan.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..9c4e30588be 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,21 @@ --- layout: page -title: AddressBook Level-3 +title: StudentConnect --- - -[![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/AY2324S1-CS2103T-F12-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-F12-2/tp/actions) +[![codecov](https://codecov.io/gh/AY2324S1-CS2103T-F12-2/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2324S1-CS2103T-F12-2/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). - -* 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. +**StudentConnect is a solution for hassle-free team formation for students to browse profiles and connect with others for the CS2103T and CS2101 group project.**
+
+While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +* If you are interested in using StudentConnect, head over to the [_Quick Start_ section of the **User Guide**](https://ay2324s1-cs2103t-f12-2.github.io/tp/UserGuide.html#quick-start). +* If you are interested about developing StudentConnect, the [**Developer Guide**](https://ay2324s1-cs2103t-f12-2.github.io/tp/DeveloperGuide.html) and [**GitHub Website**](https://github.com/AY2324S1-CS2103T-F12-2/tp) are good places to start. +* If you are interested in finding out more about the team behind StudentConnect, head over to the [**About Us Page**](https://ay2324s1-cs2103t-f12-2.github.io/tp/AboutUs.html). **Acknowledgements** -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +* This project is based on the AddressBook-Level3 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). diff --git a/docs/team/bearypop.md b/docs/team/bearypop.md new file mode 100644 index 00000000000..87725fbf41f --- /dev/null +++ b/docs/team/bearypop.md @@ -0,0 +1,42 @@ +--- +layout: page +title: Yik Leong's Project Portfolio Page +--- + +### Project: StudentConnect + +StudentConnect is a solution for hassle-free team formation for students to browse profiles and connect with others for group projects. 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: Create group +* Added a new feature that allows students to create groups in the app [\#77](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/77) +* This feature makes the process of group formation much more convenient as the students can now form groups in the app itself, instead of having to use another channel such as social media to contact other students to form groups. +* Implementing this feature required the creation of many new models and classes such as `Group` and `JsonAdaptedGroup`. The `Addressbook` class had to be modified significantly to support storing groups and the various group operations. Overall, adding this new feature was a complex process due to the large number of additions and changes that had to be made. + +### New Feature: List groups +* Added a new feature that allows students to view the list of groups in the app [\#113](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/113) +* This feature allows students to view the groups that have been created, as well as the members of each group. This will help students in finding suitable groups to join as they are able to see which groups are not yet full. +* This feature was particularly time-consuming and challenging to implement as it involved the creation of various new components in the UI to display the group information. Something noteworthy to point out is that conditional rendering is used to display either the person UI or the group UI in the window, based on the last command that the user entered. + +### New Feature: Join group +* Added a new feature that allows students to join a group in the app [\#97](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/97) +* This feature is an essential part of the group formation service that our app provides. + +### Enhancements to existing features: +* Updated the `exit` feature [\#15](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/15) + +### Testing: +* Updated tests for `add` command [\#57](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/57) +* Updated tests for `delete`, `edit` commands and Logic Manager [\#58](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/58) +* Created tests for the `Group` class [\#174](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/174) +* Created tests for `create`, `join` and `listGroup` commands [\#194](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/194), [\#208](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/208) + +### Code contributed: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=bearypop&breakdown=true) + +### Documentation: +* User Guide: + * Added documentation for the features `exit` [\#15](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/15), [\#61](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/61) + * Added documentation for the features `create`, `join` and `listGroup` [\#137](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/137) +* Developer Guide: + * Added documentation for the features `create` and the `UI` component [\#83](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/83), [\#200](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/200) 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/maj0-0.md b/docs/team/maj0-0.md new file mode 100644 index 00000000000..881e9846bef --- /dev/null +++ b/docs/team/maj0-0.md @@ -0,0 +1,52 @@ +--- +layout: page +title: Majedah's Project Portfolio Page +--- + +# Overview + +StudentConnect is the solution for hassle-free team formation for CS2103T students to browse profiles and connect with others for group projects. + +# Summary of Contributions + +## Code contributed +* Link to my [RepoSense report](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=maj0-0&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos). + +## Enhancements implemented +* **New Feature**: Tasks Command + * What it does: Lists out the set of tasks for each group. + * Highlights: Since StudentConnect is catered towards CS2103T and CS2101, the tasks are pre-set and are automatically assigned to each group. + * Challenges: Our team had initially planned to have a separate UI list for tasks, but due to time constraints, the tasks now show up in the feedback panel instead. +* **Enhancement**: Added Tutorial, Gender and Nationality fields + * Included the tutorial, nationality and gender fields to the person model. +* **Enhancement**: Updated UI for nationality and gender fields. +* **Enhancement**: List Command + * Updated the list students feature's success message to match StudentConnect context. + +## Contributions to the UG +* Under student commands and command summary, the 'list' portion. +* Under group commands and command summary, the 'tasks' portion. +* Added the "How to Use This Guide" section. +* Added the "Glossary" section. +* Updated the "Appendix" section to include requirements for all Persons' fields. +* Updated hyperlink format based on peer-review. +* Added dividers throughout UG. +* Made sure screenshots were up-to-date when features are added or updated. + +## Contributions to the DG +* Updated the diagrams and description under Model Component section. +* Under Use-cases, the 'tasks' feature. +* Updated user stories for assigned tasks. +* Under implementation section, the tutorial field and list tasks features. + +## Contributions to team-based tasks +* Did equal share of tasks assigned. +* Filmed and edited the product demo. +* Brainstormed and suggested new features during each milestone. +* Ensured all team deliverables were met in time. + +## Review / mentoring contributions +* Reviewed teammates' PRs and gave suggestions when appropriate. + +## Contributions beyond the project team +* Identified and assessed bugs in other teams' projects. diff --git a/docs/team/neyapraveen.md b/docs/team/neyapraveen.md new file mode 100644 index 00000000000..d072f10af9b --- /dev/null +++ b/docs/team/neyapraveen.md @@ -0,0 +1,55 @@ +--- +layout: page +title: Neya's Project Portfolio Page +--- +# Overview + +StudentConnect is the solution for hassle-free team formation for CS2103T students to browse profiles and connect with others for group work. + +# Summary of Contributions + +## Code contributed +* Link to my [Reposense Report](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=neya&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22) + +## Enhancements implemented +* **Enhancement**: Changed `delete` feature to delete by email rather than by index. +* **Enhancement**: Redid the UI, making styling decisions such as colour scheme, fonts. +* **Enhancement**: Edited UI for GroupListPanel, so it would resemble PersonListPanel. +* **New Feature**: Added hyperlinks to the social media field, so users can open the urls in a browser. +* **New Feature**: Implemented the `deleteGroup` command. +* **New Feature**: Implemented the `leave` command. +* **New Feature**: Added classes for `Todo`, `Deadlines` and `Tasklist` before implementation of task functions. +* **New Feature**: Implemented and styled a confirmation pop-up for clear command (including css). + +## Contributions to testing +* Changed test cases pertaining to `delete` feature, `clear` feature. +* Created a `JavaFXInitialiser` to be used for fx and ui related tests. +* Implemented `PersonCardTest`.`deleteGroupCommandTest`, `deleteGroupCommandParserTest`, `leaveCommandTest`, `leaveCommandParserTest`. + +## Contributions to the UG +* Added documentation for the feature `delete`, `clear`, including the usage of the pop-up, `leave`, `deleteGroup` and social media hyperlinks. +* Created Table of Contents. +* Reformatted UG according to general commands, person commands and group commands. +* Wrote the Introduction section and created the Appendix section. +* Added page breaks for formatting and back to table of contents hyperlink at the end of pages. +* Added relevant hyperlinks like download link for java 11 and warning messages. +* Added hyperlinks of glossary terms. + +## Contributions to the DG +* Updated initial contents in DG after discussion(target user profile, value proposition, user stories, use cases, NFRs, Glossary). +* Under Use-cases, `Delete a student`, `Access social media` , `Clearing all data`, `Delete a group`, `Leave a group`. +* Add relevant user stories for clear, delete group, leave group, social media links, delete. +* Rearranged all use cases and numbered all for easy reference. +* Added sequence diagram using PlantUML. + +## Contributions to team-based tasks +* Did equal share of tasks assigned. +* Wrote intro and Brainstormed ideas for features for further iterations. + +## Review/ mentoring contributions +* Reviewed teammates' PRs and gave suggestions when appropriate. +* Helped with teammates' UI and debugging, ensured code quality, helped in adding javadocs. + +## Contributions beyond the project team +* Participated in load testing, helped in finding other group's bugs during PED. +* Helped peer in forum. diff --git a/docs/team/pearlynnt.md b/docs/team/pearlynnt.md new file mode 100644 index 00000000000..72dbbf6aa6c --- /dev/null +++ b/docs/team/pearlynnt.md @@ -0,0 +1,59 @@ +--- +layout: page +title: Pearlynn Toh's Project Portfolio Page +--- + +### Overview +StudentConnect is the solution for hassle-free team formation for CS2103T students to browse profiles and connect with others for group work. + +### Summary of Contributions +Given below are my contributions to the project. + +**Code contributed** +* Link to my [RepoSense report](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=pearlynnt&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + +**Enhancements implemented** +* **Edit Student Feature**: Added the ability to edit a student's personal details + * What it does: This feature allows the user to edit a student's personal details in the system. + * Justification: This feature helps users to keep their personal details up-to-date. + * Highlights: Modified the code to allow the new student fields to be editable. +* **Find Students Feature**: Added the ability to find student(s) by name + * What it does: This feature allows the user to find student(s) by name with any combination of partial keyword(s). + * Justification: This feature helps users to find other students more quickly and easily. + * Highlights: Modified the code to check if each word in the students' name contains the keyword(s) instead of having to match the keyword(s). +* **Filter Students Feature**: Added the ability to filter students by tutorial + * What it does: This feature allows the user to filter students by tutorial with any combination of tutorial slot(s). + * Justification: This feature helps users to search for other students who are also interested in attending or are assigned to the same tutorial slot. + * Highlights: A new Predicate class had to be written to support this feature. Invalid tutorial slot(s) provided by the user had to be handled gracefully. +* **Find Group Feature**: Added the ability to find a group by group number + * What it does: This feature allows the user to find a project group by a group number. + * Justification: This feature helps users to find a specific group more quickly and easily. + * Highlights: A new Predicate class had to be written to support this feature. Invalid group number provided by the user had to be handled gracefully. +* **Filter Groups Feature**: Added the ability to filter groups by tutorial + * What it does: This feature allows the user to filter groups by a tutorial slot. + * Justification: This feature helps users to search for groups that belong to a particular tutorial that they may prefer. + * Highlights: A new Predicate class had to be written to support this feature. Invalid tutorial slot provided by the user had to be handled gracefully. +* **Check Group Feature**: Added the ability to check if a group fulfils the diversity requirements of the course + * What it does: This feature allows the user to check the composition of a group's members. + * Justification: This feature helps users to check if a group fulfils the diversity group formation requirements of the course with a single command. + * Highlights: Different warning messages were written to inform users of what the group may fall short of in terms of group formation. Extensive test cases and test data were written to rigorously test the different possible composition of the group members. + +**Contributions to the UG** +* Added the 'Edit Command', 'Find Command', 'Filter Command', 'Find Group Command', 'Filter Group Command', 'Check Command' sections. +* Ensured consistent formatting of the content in the User Guide. + +**Contributions to the DG** +* Added the 'Edit a Student', 'Find a student', 'Filter students', 'Find a group', 'Filter groups', 'Check a group' use cases. +* Updated the Storage Class Diagram using PlantUML. + +**Contributions to team-based tasks** +* Set up the team's Github Repository, and project documentation. +* Managed the milestones on Github. +* Led the team's project meetings. + +**Review/mentoring contributions** +* Reviewed the team's Github pull requests and gave comments where appropriate. (e.g., [#106](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/106), [#177](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/177), [#190](https://github.com/AY2324S1-CS2103T-F12-2/tp/pull/190)) + +**Contributions beyond the project team** +* Tested another team's product and reported bugs during PE-D. ([list of issues](https://github.com/PearlynnT/ped/issues)) +* Participated in load testing. diff --git a/docs/team/wnchan.md b/docs/team/wnchan.md new file mode 100644 index 00000000000..ccef70a82b4 --- /dev/null +++ b/docs/team/wnchan.md @@ -0,0 +1,51 @@ +--- +layout: page +title: Wei Ning's Project Portfolio Page +--- +# Overview + +StudentConnect is the solution for hassle-free team formation for CS2101/CS2103T students to browse profiles and connect with others for group work. + +# Summary of Contributions + +## Code contributed +* Link to my [**RepoSense report**](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&chartGroupIndex=23&chartIndex=1) + +## Enhancements implemented +* **Updated Feature**: Added the ability to `add` the student's personal details into the system. + * Highlights: This enhancement affects existing commands and commands to be added. + * Added the different field classes and other classes associated with the feature too. + * Challenges: Dealing with errors that occurred while changing the other classes associated. + +* **Updated Feature**: Helps the user to find the user guide if they need `help` and see requirements of CS2101/CS2103T groupings. + * Highlights: Updated help window to be more useful for our app and target audience. + * Added test cases, updated UI of the help window and added relevant information. + * Challenges: Adjusting the layout was quite challenging as a beginner in FXML. + +* **New Feature**: Added the ability to `mark` a specified task in a specified group. + * Highlights: This allows users to keep track of tasks and mark it as done. + * Added test cases for the feature. + +* **New Feature**: Added the ability to `unmark` a specified task in a specified group. + * Highlights: This allows users to keep track of tasks and mark it as not done. + * Added test cases for the feature. + +## Contributions to the UG +* Under features and command summary, the `add`, `help`, `mark` and `unmark` portion. +* Helped standardise messages, added logo and valid major list (in appendix) in the UG. + +## Contributions to the DG +* Brainstormed as a group for the details included in the DG. +* Under Use-cases, the `add`, `help`, `mark` and `unmark` feature. +* Updated the sequence diagram under `Logic Component` section and `Appendix: Instructions for manual testing` to include all the commands for StudentConnect. + +## Contributions to team-based tasks +* Did equal share of tasks assigned and brainstormed features that were implemented. +* Created the StudentConnect logo, updated README.md and index.md. + +## Review/ mentoring contributions +* Review other members' pull requests, gave ideas and suggestions. +* Help members with debugging and test cases. + +## Contributions beyond the project team +* Helped in the load testing, found bugs of other team's project during PE-D and PE. diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index a97a86ee8d7..873bae6a3f5 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -79,4 +79,5 @@ public String toString() { .add("windowCoordinates", windowCoordinates) .toString(); } + } diff --git a/src/main/java/seedu/address/commons/util/JsonParserUtil.java b/src/main/java/seedu/address/commons/util/JsonParserUtil.java new file mode 100644 index 00000000000..043d8e66dae --- /dev/null +++ b/src/main/java/seedu/address/commons/util/JsonParserUtil.java @@ -0,0 +1,26 @@ +package seedu.address.commons.util; + +import java.io.IOException; + +import seedu.address.model.AddressBook; + +/** + * Utility class for parsing JSON data to an AddressBook object. + */ +public class JsonParserUtil { + + /** + * Parses a JSON string into an AddressBook object. + * + * @param jsonString The JSON string to be parsed. + * @return The parsed AddressBook object. + * @throws IOException If there is an issue with the I/O operation during parsing. + */ + public static AddressBook parseJsonToAddressBook(String jsonString) throws IOException { + try { + return JsonUtil.fromJsonString(jsonString, AddressBook.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..ffe8585301f 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,56 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code word}. + * Ignores case, accepts a partial word match. + *
examples:
+     *       containsIgnoreCase("ABc def", "abc") == true
+     *       containsIgnoreCase("ABc def", "DEF") == true
+     *       containsIgnoreCase("ABc def", "AB") == true
+     *       containsIgnoreCase("ABc def", "abcd") == false
+     *       
+ * + * @param sentence cannot be null + * @param str cannot be null, cannot be empty, must be a single string + */ + public static boolean containsIgnoreCase(String sentence, String str) { + requireNonNull(sentence); + requireNonNull(str); + + String preppedStr = str.trim(); + checkArgument(!preppedStr.isEmpty(), "Word parameter cannot be empty"); + checkArgument(preppedStr.split("\\s+").length == 1, "Word parameter should be a single word"); + + String preppedSentence = sentence; + String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + + return Arrays.stream(wordsInPreppedSentence) + .anyMatch(word -> word.toLowerCase().contains(preppedStr.toLowerCase())); + } + + /** + * Returns true if the {@code list} contains the {@code tut}. + * A full tutorial match is required. + * + * @param list cannot be null + * @param tut cannot be null, cannot be empty, must be a single number + */ + public static boolean containsTutorial(String list, String tut) { + requireNonNull(list); + requireNonNull(tut); + + String preppedTut = tut.trim(); + checkArgument(!preppedTut.isEmpty(), "Tutorial parameter cannot be empty"); + checkArgument(preppedTut.split("\\s+").length == 1, "Tutorial parameter should be a single number"); + + String preppedList = list; + String[] tutsInPreppedList = preppedList.split("\\s+"); + + return Arrays.stream(tutsInPreppedList) + .anyMatch(preppedTut::contentEquals); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..663c9b94aea 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,6 +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.group.Group; import seedu.address.model.person.Person; /** @@ -33,6 +34,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of groups */ + ObservableList getFilteredGroupList(); + /** * 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 5aa3b91c7d0..8b28747c12a 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,6 +15,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -71,6 +72,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredGroupList() { + return model.getFilteredGroupList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..dbfa450f004 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,6 +5,7 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -15,12 +16,17 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_EMAIL = "The email provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_GROUPS_LISTED_OVERVIEW = "%1$d groups listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; /** - * Returns an error message indicating the duplicate prefixes. + * Returns an error message indicating duplicate prefixes. + * + * @param duplicatePrefixes An array of Prefixes that are duplicated. + * @return Error message indicating duplicate prefixes. */ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePrefixes) { assert duplicatePrefixes.length > 0; @@ -32,19 +38,33 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref } /** - * Formats the {@code person} for display to the user. + * Formats the given {@code person} for display to the user. + * + * @param person The Person object to be formatted. + * @return A formatted string representing the Person's information. */ public static String format(Person person) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) - .append("; Phone: ") - .append(person.getPhone()) - .append("; Email: ") - .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + builder.append("Name: ").append(person.getName()) + .append("; Major: ").append(person.getMajor()) + .append("; Year: ").append(person.getYear()) + .append("; Email: ").append(person.getEmail()) + .append("; Description: ").append(person.getDescription()) + .append("; Tutorial: ").append(person.getTutorials()) + .append("; Social Media: ").append(person.getSocialMediaLinks()); + return builder.toString(); + } + + /** + * Formats the given {@code group} for display to the user. + * + * @param group The Group object to be formatted. + * @return A formatted string representing the Group's information. + */ + public static String format(Group group) { + final StringBuilder builder = new StringBuilder(); + builder.append("Group Number: ").append(group.getNumber()) + .append("; Members: ").append(group.getMembers()); // todo: needs to be edited, formatting is wrong return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..79adc5dd98c 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,11 +1,15 @@ 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_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NATIONALITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL_MEDIA_LINK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -14,29 +18,37 @@ import seedu.address.model.person.Person; /** - * Adds a person to the address book. + * Adds a person to StudentConnect. */ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to StudentConnect. " + "Parameters: " + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " + + PREFIX_MAJOR + "MAJOR " + + PREFIX_YEAR + "YEAR " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_DESCRIPTION + "DESCRIPTION " + + "[" + PREFIX_TUTORIAL + "TUTORIAL]... " + + "[" + PREFIX_SOCIAL_MEDIA_LINK + "SOCIAL_MEDIA_LINK]... " + + PREFIX_NATIONALITY + "NATIONALITY " + + PREFIX_GENDER + "GENDER \n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_MAJOR + "Computer Science " + + PREFIX_YEAR + "2 " + + PREFIX_EMAIL + "johnd@u.nus.edu " + + PREFIX_DESCRIPTION + "I love programming in my free time " + + PREFIX_TUTORIAL + "02 " + + PREFIX_SOCIAL_MEDIA_LINK + "https://www.linkedin.com/in/john-doe-123456789 " + + PREFIX_NATIONALITY + "local " + + PREFIX_GENDER + "M"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "Details added successfully! New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This student is already on StudentConnect as this " + + "email has already been used."; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/CheckCommand.java b/src/main/java/seedu/address/logic/commands/CheckCommand.java new file mode 100644 index 00000000000..fd9c304ac0c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckCommand.java @@ -0,0 +1,209 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Set; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.tutorial.Tutorial; + +/** + * Checks a group requirement by its group number. + */ +public class CheckCommand extends Command { + + public static final String COMMAND_WORD = "checkGroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Checks the group identified by its group number.\n" + + "Parameters: GROUP_NUMBER\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String GROUP_NUM = "Group %1$s\n"; + public static final String MESSAGE_CHECK_GROUP_SUCCESS = + "Group fulfils the diversity requirements of CS2103T."; + public static final String MESSAGE_HELP = + "You can enter the `help` command for more information on group requirements."; + public static final String MESSAGE_CHECK_GROUP_SIZE_EMPTY = + "Group does not have any members.\n"; + public static final String MESSAGE_CHECK_GROUP_SIZE_ONE = + "Group has only one member.\n"; + public static final String MESSAGE_CHECK_GROUP_SIZE_UNDER = + "Group has less than 5 members.\n"; + public static final String MESSAGE_CHECK_GROUP_SIZE_OVER = + "Group size has exceeded limit with more than 5 members.\n"; + public static final String MESSAGE_CHECK_GROUP_NATIONALITY_WARNING = + "Group comprises of members of the same nationality.\n"; + public static final String MESSAGE_CHECK_GROUP_GENDER_WARNING = + "Group comprises of members of the same gender.\n"; + public static final String MESSAGE_CHECK_GROUP_TUTORIAL_WARNING = + "Not every group member's tutorial matches the group's tutorial.\n"; + public static final String MESSAGE_CHECK_GROUP_NOT_FOUND = "Group with the provided group number not found."; + private final GroupContainsKeywordsPredicate predicate; + + + private final int groupNumber; + + /** + * @param groupNumber unique identifier of the group + * @param predicate check group number + */ + public CheckCommand(int groupNumber, GroupContainsKeywordsPredicate predicate) { + this.groupNumber = groupNumber; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredGroupList(predicate); + + // Check if the group with the provided group number exists + Group groupToCheck = model.getGroupWithNumber(groupNumber) + .orElseThrow(() -> new CommandException(MESSAGE_CHECK_GROUP_NOT_FOUND)); + + String message = ""; + boolean isSuccess = true; + Set groupMembers = groupToCheck.getMembers(); + + // check group size + if (groupMembers.isEmpty()) { + isSuccess = false; + message = MESSAGE_CHECK_GROUP_SIZE_EMPTY; + } else if (groupMembers.size() == 1) { + isSuccess = false; + message = MESSAGE_CHECK_GROUP_SIZE_ONE; + } else if (groupMembers.size() > 1 && groupMembers.size() < 5) { + isSuccess = false; + message = MESSAGE_CHECK_GROUP_SIZE_UNDER; + } else if (groupMembers.size() > 5) { + isSuccess = false; + message = MESSAGE_CHECK_GROUP_SIZE_OVER; + } + + if (groupMembers.size() > 1) { + String groupTutorial = groupToCheck.getTutorial().value; + isSuccess = hasMixNationality(groupMembers) && hasMixGender(groupMembers) + && hasGroupTutorial(groupMembers, groupTutorial); + + if (!hasMixNationality(groupMembers)) { + message += MESSAGE_CHECK_GROUP_NATIONALITY_WARNING; + } + if (!hasMixGender(groupMembers)) { + message += MESSAGE_CHECK_GROUP_GENDER_WARNING; + } + if (!hasGroupTutorial(groupMembers, groupTutorial)) { + message += MESSAGE_CHECK_GROUP_TUTORIAL_WARNING; + } + } + + if (isSuccess) { + return new CommandResult(String.format(GROUP_NUM + MESSAGE_CHECK_GROUP_SUCCESS, groupToCheck.getNumber()), + false, false, true, false); + } else { + return new CommandResult(String.format(GROUP_NUM + message + MESSAGE_HELP, groupToCheck.getNumber()), + false, false, true, false); + } + } + + /** + * Checks if a group has a mix of local and foreigner members. + * + * @param groupMembers The set of persons representing the group members. + * @return True if there is a mix of local and foreigner members, false otherwise. + */ + private boolean hasMixNationality(Set groupMembers) { + int localCount = 0; + int foreignerCount = 0; + for (Person member : groupMembers) { + if (member.getNationality().value.equals("local")) { + localCount++; + } else { + foreignerCount++; + } + } + + if (localCount == 0 || foreignerCount == 0) { + return false; + } + return true; + } + + /** + * Checks if a group has a mix of male and female members. + * + * @param groupMembers The set of persons representing the group members. + * @return True if there is a mix of male and female members, false otherwise. + */ + private boolean hasMixGender(Set groupMembers) { + int maleCount = 0; + int femaleCount = 0; + for (Person member : groupMembers) { + if (member.getGender().value.equals("M")) { + maleCount++; + } else { + femaleCount++; + } + } + + if (maleCount == 0 || femaleCount == 0) { + return false; + } + return true; + } + + /** + * Checks if all members of a group belong to a specific tutorial. + * + * @param groupMembers The set of persons representing the group members. + * @param groupTutorial The tutorial code to check against. + * @return True if all members belong to the specified tutorial, false otherwise. + */ + private boolean hasGroupTutorial(Set groupMembers, String groupTutorial) { + int count = 0; + for (Person member : groupMembers) { + for (Tutorial tut : member.getTutorials()) { + if (tut.value.equals((groupTutorial))) { + count++; + continue; + } + } + } + + if (count != groupMembers.size()) { + return false; + } + return true; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CheckCommand)) { + return false; + } + + CheckCommand otherCheckCommand = (CheckCommand) other; + return groupNumber == otherCheckCommand.groupNumber; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("groupNumber", groupNumber) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..ebc14d505ca 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -1,23 +1,48 @@ package seedu.address.logic.commands; -import static java.util.Objects.requireNonNull; - +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; +import seedu.address.ui.ConfirmationPopup; /** - * Clears the address book. + * Clears the address book with a confirmation popup. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "All student data has been cleared."; + + public static final String SHOWING_CONFIRMATION_MESSAGE = "Opened confirmation window. " + + "Please ensure you use the exit command when exiting StudentConnect for successful reset."; + + private boolean isConfirmed = false; @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); + public CommandResult execute(Model model) throws CommandException { + ConfirmationPopup confirmationPopup = new ConfirmationPopup(); + confirmationPopup.setConfirmationCallback(confirmed -> { + if (confirmed) { + this.isConfirmed = true; + model.setAddressBook(new AddressBook()); + } + }); + confirmationPopup.show(); + + if (isConfirmed) { + return new CommandResult(SHOWING_CONFIRMATION_MESSAGE, false, false, false, true); + } else { + return new CommandResult(SHOWING_CONFIRMATION_MESSAGE, false, false, false, false); + } + } + + /** + * Sets the confirmation status of the popup. + * + * @param isConfirmed The confirmation status to be set. True if confirmed, false otherwise. + */ + public void setConfirmed(boolean isConfirmed) { + this.isConfirmed = isConfirmed; } } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..d9f2fadde2e 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -6,6 +6,7 @@ import seedu.address.commons.util.ToStringBuilder; + /** * Represents the result of a command execution. */ @@ -13,19 +14,37 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; + /** + * Group UI should be shown. + */ + private final boolean groupCommand; + + /** + * Data should be cleared. + */ + private final boolean clear; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + + public CommandResult(String feedbackToUser, boolean showHelp, + boolean exit, boolean groupCommand, boolean clear) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.groupCommand = groupCommand; + this.clear = clear; } /** @@ -33,7 +52,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false, false); } public String getFeedbackToUser() { @@ -48,6 +67,14 @@ public boolean isExit() { return exit; } + public boolean isGroupCommand() { + return groupCommand; + } + + public boolean isClear() { + return clear; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -62,12 +89,14 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && clear == otherCommandResult.clear + && groupCommand == otherCommandResult.groupCommand; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, groupCommand, clear); } @Override @@ -76,7 +105,8 @@ public String toString() { .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) .add("exit", exit) + .add("groupCommand", groupCommand) + .add("clear", clear) .toString(); } - } diff --git a/src/main/java/seedu/address/logic/commands/CreateCommand.java b/src/main/java/seedu/address/logic/commands/CreateCommand.java new file mode 100644 index 00000000000..aa5cbd1dc7b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CreateCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; +import seedu.address.model.tutorial.Tutorial; + +/** + * Creates a new empty group. + */ +public class CreateCommand extends Command { + + public static final String COMMAND_WORD = "create"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a new empty group.\n" + + "Parameters: " + + PREFIX_TUTORIAL + "TUTORIAL " + + "Example: " + COMMAND_WORD + " " + + PREFIX_TUTORIAL + "02"; + + public static final String MESSAGE_SUCCESS = "Group created successfully! Group number is %1$s"; + + private final Tutorial tutorial; + + public CreateCommand(Tutorial tutorial) { + this.tutorial = tutorial; + } + + @Override + public CommandResult execute(Model model) { + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + int number = generateGroupNumber(model); + Group createdGroup = new Group(number, tutorial); + model.addGroup(createdGroup); + + return new CommandResult(String.format(MESSAGE_SUCCESS, createdGroup.getNumber()), + false, false, true, false); + } + + /** + * Generates the next group number to be used when creating a new group. + * + * @param model + */ + public int generateGroupNumber(Model model) { + int number = 1; + ReadOnlyAddressBook addressBook = model.getAddressBook(); + ObservableList groups = addressBook.getGroupList(); + + if (!groups.isEmpty()) { + List groupNumbers = groups.stream() + .map(Group::getNumber).collect(Collectors.toList()); + while (groupNumbers.contains(number)) { + number++; + } + } + + return number; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CreateCommand)) { + return false; + } + + CreateCommand otherCreateCommand = (CreateCommand) other; + return tutorial.equals(otherCreateCommand.tutorial); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..058594294d8 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,48 +1,64 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.List; +import java.util.Optional; -import seedu.address.commons.core.index.Index; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Email; import seedu.address.model.person.Person; /** - * 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" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the student identified by the email address.\n" + + "Parameters: EMAIL\n" + + "Example: " + COMMAND_WORD + " alexyeoh@u.nus.edu"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Student deleted Successfully! Deleted Student: %1$s"; + public static final String MESSAGE_DELETE_EMAIL_NOT_FOUND = "Student with the provided email not found."; + private final Email targetEmail; - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + public DeleteCommand(Email targetEmail) { + this.targetEmail = targetEmail; } @Override public CommandResult execute(Model model) throws CommandException { + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + // Find the person with the provided email + Optional personToDelete = model.getPersonWithEmail(targetEmail); + + if (personToDelete.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMAIL_NOT_FOUND); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); + // Check if the person is in any groups + for (Group group : model.getAddressBook().getGroupList()) { + if (group.hasMember(personToDelete.get())) { + // If the person is in a group, remove them from the group + model.removePersonFromGroup(personToDelete.get(), group); + } + } + + // Delete the person from the model + model.deletePerson(personToDelete.get()); + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete.get()))); } @Override @@ -57,13 +73,13 @@ public boolean equals(Object other) { } DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); + return targetEmail.equals(otherDeleteCommand.targetEmail); } @Override public String toString() { return new ToStringBuilder(this) - .add("targetIndex", targetIndex) + .add("targetEmail", targetEmail) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java new file mode 100644 index 00000000000..a41d454fb33 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Deletes a group identified by its group number. + */ +public class DeleteGroupCommand extends Command { + public static final String COMMAND_WORD = "deleteGroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the group identified by its group number.\n" + + "Parameters: gr/GROUP_NUMBER\n" + + "Example: " + COMMAND_WORD + " gr/1"; + + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Group deleted successfully! Deleted Group: %1$s"; + public static final String MESSAGE_DELETE_GROUP_NOT_FOUND = "Group with the provided group number not found."; + + private final int groupNumber; + + /** + * Creates a DeleteGroupCommand to delete the group with the specified group number. + * + * @param groupNumber The group number of the group to be deleted. + */ + public DeleteGroupCommand(int groupNumber) { + this.groupNumber = groupNumber; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + requireNonNull(model); + + // Check if the group with the provided group number exists + Group groupToDelete = model.getGroupWithNumber(groupNumber) + .orElseThrow(() -> new CommandException(MESSAGE_DELETE_GROUP_NOT_FOUND)); + + // Delete the group from the model + model.deleteGroup(groupToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, groupToDelete.getNumber()), + false, false, true, false); + } + + /** + * Gets the group number of the group to be deleted. + * + * @return The group number. + */ + public int getGroupNumber() { + return groupNumber; + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..f50667d128c 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,90 +1,111 @@ 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_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NATIONALITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL_MEDIA_LINK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; +import seedu.address.model.group.Group; +import seedu.address.model.person.Description; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nationality; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Year; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing student in StudentConnect. */ 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 email used in the displayed student list. " + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Parameters: EMAIL (must end with u.nus.edu) " + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_MAJOR + "MAJOR] " + + "[" + PREFIX_YEAR + "YEAR] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_TUTORIAL + "TUTORIAL]... " + + "[" + PREFIX_SOCIAL_MEDIA_LINK + "SOCIAL_MEDIA_LINK]... " + + "[" + PREFIX_NATIONALITY + "NATIONALITY] " + + "[" + PREFIX_GENDER + "GENDER] \n" + + "Example: " + COMMAND_WORD + " johnd@u.nus.edu " + + PREFIX_YEAR + "3 " + + PREFIX_EMAIL + "johndoe@u.nus.edu"; + + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Details edited successfully! 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_PERSON = "This student is already on StudentConnect as this " + + "email has already been used."; + public static final String MESSAGE_EMAIL_NOT_FOUND = "Student with the provided email not found."; - private final Index index; + private final Email email; private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param email of the person in the filtered student list to edit + * @param editPersonDescriptor details to edit the student with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); + public EditCommand(Email email, EditPersonDescriptor editPersonDescriptor) { + requireNonNull(email); requireNonNull(editPersonDescriptor); - this.index = index; + this.email = email; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + // Find the person with the provided email + Optional personToEdit = model.getPersonWithEmail(email); - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (personToEdit.isEmpty()) { + throw new CommandException(MESSAGE_EMAIL_NOT_FOUND); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person editedPerson = createEditedPerson(personToEdit.get(), editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + if (!personToEdit.get().isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(personToEdit.get(), editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + if (model.personIsInAGroup(editedPerson)) { + Group targetGroup = model.getGroupThatPersonIsIn(personToEdit.get()); + targetGroup.removeMember(personToEdit.get()); + targetGroup.addMember(editedPerson); + } return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); } @@ -96,12 +117,19 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript assert personToEdit != null; Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Major updatedMajor = editPersonDescriptor.getMajor().orElse(personToEdit.getMajor()); + Year updatedYear = editPersonDescriptor.getYear().orElse(personToEdit.getYear()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Description updatedDescription = editPersonDescriptor.getDescription().orElse(personToEdit.getDescription()); + Set updatedTutorials = editPersonDescriptor.getTutorials() + .orElse(personToEdit.getTutorials()); + Set updatedSocialMediaLinks = editPersonDescriptor.getSocialMediaLinks() + .orElse(personToEdit.getSocialMediaLinks()); + Nationality updatedNationality = editPersonDescriptor.getNationality().orElse(personToEdit.getNationality()); + Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); + + return new Person(updatedName, updatedMajor, updatedYear, updatedEmail, updatedDescription, + updatedTutorials, updatedSocialMediaLinks, updatedNationality, updatedGender); } @Override @@ -116,48 +144,57 @@ public boolean equals(Object other) { } EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) + return email.equals(otherEditCommand.email) && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); } @Override public String toString() { return new ToStringBuilder(this) - .add("index", index) + .add("email", email) .add("editPersonDescriptor", editPersonDescriptor) .toString(); } /** - * 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 { private Name name; - private Phone phone; + private Major major; + private Year year; private Email email; - private Address address; - private Set tags; + private Description description; + private Set tutorials; + private Set socialMediaLinks; + private Nationality nationality; + private Gender gender; public EditPersonDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code socialMediaLinks} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); - setPhone(toCopy.phone); + setMajor(toCopy.major); + setYear(toCopy.year); setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); + setDescription(toCopy.description); + setTutorials(toCopy.tutorials); + setSocialMediaLinks(toCopy.socialMediaLinks); + setNationality(toCopy.nationality); + setGender(toCopy.gender); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, major, year, email, description, + tutorials, socialMediaLinks, nationality, gender); } public void setName(Name name) { @@ -168,12 +205,20 @@ public Optional getName() { return Optional.ofNullable(name); } - public void setPhone(Phone phone) { - this.phone = phone; + public void setMajor(Major major) { + this.major = major; + } + + public Optional getMajor() { + return Optional.ofNullable(major); } - public Optional getPhone() { - return Optional.ofNullable(phone); + public void setYear(Year year) { + this.year = year; + } + + public Optional getYear() { + return Optional.ofNullable(year); } public void setEmail(Email email) { @@ -184,29 +229,62 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + /** + * Sets {@code tutorials} to this object's {@code tutorials}. + * A defensive copy of {@code tutorials} is used internally. + */ + public void setTutorials(Set tutorials) { + this.tutorials = (tutorials != null) ? new HashSet<>(tutorials) : null; + } + + public Optional getNationality() { + return Optional.ofNullable(nationality); + } + + public void setNationality(Nationality nationality) { + this.nationality = nationality; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getGender() { + return Optional.ofNullable(gender); + } + + public void setGender(Gender gender) { + this.gender = gender; + } + /** + * Returns an unmodifiable tutorial set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tutorials} is null. + */ + public Optional> getTutorials() { + return (tutorials != null) ? Optional.of(Collections.unmodifiableSet(tutorials)) : Optional.empty(); } /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Sets {@code socialMediaLinks} to this object's {@code socialMediaLinks}. + * A defensive copy of {@code socialMediaLinks} is used internally. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setSocialMediaLinks(Set socialMediaLinks) { + this.socialMediaLinks = (socialMediaLinks != null) ? new HashSet<>(socialMediaLinks) : null; } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * Returns an unmodifiable social media link set, which throws {@code UnsupportedOperationException} * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns {@code Optional#empty()} if {@code socialMediaLinks} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional> getSocialMediaLinks() { + return (socialMediaLinks != null) ? Optional.of(Collections.unmodifiableSet(socialMediaLinks)) + : Optional.empty(); } @Override @@ -222,20 +300,28 @@ public boolean equals(Object other) { EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) + && Objects.equals(major, otherEditPersonDescriptor.major) + && Objects.equals(year, otherEditPersonDescriptor.year) && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(description, otherEditPersonDescriptor.description) + && Objects.equals(tutorials, otherEditPersonDescriptor.tutorials) + && Objects.equals(socialMediaLinks, otherEditPersonDescriptor.socialMediaLinks) + && Objects.equals(nationality, otherEditPersonDescriptor.nationality) + && Objects.equals(gender, otherEditPersonDescriptor.gender); } @Override public String toString() { return new ToStringBuilder(this) .add("name", name) - .add("phone", phone) + .add("major", major) + .add("year", year) .add("email", email) - .add("address", address) - .add("tags", tags) + .add("description", description) + .add("tutorials", tutorials) + .add("social media links", socialMediaLinks) + .add("nationality", nationality) + .add("gender", gender) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..6c2ab8933ea 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,11 +9,12 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Thank you for using StudentConnect!\n" + + "Exiting the application now…"; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java new file mode 100644 index 00000000000..bf32a97b6d5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.tutorial.TutorialContainsSlotsPredicate; + +/** + * Filters all students in StudentConnect whose tutorial slots match the filter slots. + */ +public class FilterCommand extends Command { + + public static final String COMMAND_WORD = "filter"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all students whose tutorials match any of " + + "the specified slots (2-digit numbers between 01 and 22) and displays them as a list with index numbers." + + "\nParameters: SLOT [MORE_SLOTS]...\n" + + "Example: " + COMMAND_WORD + " 08 15"; + + private final TutorialContainsSlotsPredicate predicate; + + public FilterCommand(TutorialContainsSlotsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterCommand)) { + return false; + } + + FilterCommand otherFilterCommand = (FilterCommand) other; + return predicate.equals(otherFilterCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterGroupCommand.java b/src/main/java/seedu/address/logic/commands/FilterGroupCommand.java new file mode 100644 index 00000000000..9428a642023 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterGroupCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.group.GroupBelongsTutorialPredicate; + +/** + * Filters all groups in StudentConnect that belong to the filtered tutorial slot. + */ +public class FilterGroupCommand extends Command { + + public static final String COMMAND_WORD = "filterGroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all groups that belong to the specified " + + "tutorial slot (2-digit numbers between 01 and 22) and displays them as a list with index numbers." + + "\nParameters: SLOT\n" + + "Example: " + COMMAND_WORD + " 01"; + + private final GroupBelongsTutorialPredicate predicate; + + public FilterGroupCommand(GroupBelongsTutorialPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredGroupList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_GROUPS_LISTED_OVERVIEW, model.getFilteredGroupList().size()), + false, false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterGroupCommand)) { + return false; + } + + FilterGroupCommand otherFilterGroupCommand = (FilterGroupCommand) other; + return predicate.equals(otherFilterGroupCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..c21cd98c3d7 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,6 +1,8 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -8,14 +10,14 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all students in StudentConnect whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; @@ -29,6 +31,8 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { @Override public CommandResult execute(Model model) { requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); model.updateFilteredPersonList(predicate); return new CommandResult( String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); diff --git a/src/main/java/seedu/address/logic/commands/FindGroupCommand.java b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java new file mode 100644 index 00000000000..f228f71491e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Finds and lists all groups in StudentConnect whose group contains any of the argument keywords. + */ +public class FindGroupCommand extends Command { + + public static final String COMMAND_WORD = "findGroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all groups whose number contain any of " + + "the specified keywords and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " 1 5 10"; + + private final GroupContainsKeywordsPredicate predicate; + + public FindGroupCommand(GroupContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredGroupList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_GROUPS_LISTED_OVERVIEW, model.getFilteredGroupList().size()), + false, false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindGroupCommand)) { + return false; + } + + FindGroupCommand otherFindGroupCommand = (FindGroupCommand) other; + return predicate.equals(otherFindGroupCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..1faf8126726 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -1,5 +1,8 @@ package seedu.address.logic.commands; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + import seedu.address.model.Model; /** @@ -16,6 +19,8 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/JoinCommand.java b/src/main/java/seedu/address/logic/commands/JoinCommand.java new file mode 100644 index 00000000000..b402052d307 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/JoinCommand.java @@ -0,0 +1,118 @@ +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_GROUP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Optional; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; + +/** + * Adds a student specified by email to a group specified by group number. + */ +public class JoinCommand extends Command { + + public static final String COMMAND_WORD = "join"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds the student specified by email to the group specified by group number.\n" + + "Group number must be a positive integer. The maximum number of members a group can have is 5.\n" + + "Parameters: " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_GROUP + "GROUP NUMBER\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_EMAIL + "johnd@u.nus.edu " + + PREFIX_GROUP + "1"; + + public static final String MESSAGE_JOIN_SUCCESS = "Join successful! %1$s has joined Group %2$s!"; + public static final String MESSAGE_JOIN_EMAIL_NOT_FOUND = "Student with the provided email not found."; + public static final String MESSAGE_JOIN_GROUP_NOT_FOUND = "Group with the provided group number not found."; + public static final String MESSAGE_PERSON_ALREADY_IN_GROUP = "The provided student is " + + "already a member of the provided group."; + public static final String MESSAGE_GROUP_FULL = "Join failed as the group already has 5 members."; + public static final String MESSAGE_PERSON_IN_ANOTHER_GROUP = "The provided student is already in another group."; + + private final Email targetEmail; + private final int targetGroupNumber; + + /** + * Constructs a {@code JoinCommand} with the specified email and group number to join a group. + * + * @param targetEmail of the student to be added to the group + * @param targetGroupNumber group number + */ + public JoinCommand(Email targetEmail, int targetGroupNumber) { + this.targetEmail = targetEmail; + this.targetGroupNumber = targetGroupNumber; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + // Find the person with the provided email + Optional personToJoin = model.getPersonWithEmail(targetEmail); + + if (personToJoin.isEmpty()) { + throw new CommandException(MESSAGE_JOIN_EMAIL_NOT_FOUND); + } + + // Find the group with the provided email + Optional groupToJoin = model.getGroupWithNumber(targetGroupNumber); + + if (groupToJoin.isEmpty()) { + throw new CommandException(MESSAGE_JOIN_GROUP_NOT_FOUND); + } + + if (groupToJoin.get().hasMember(personToJoin.get())) { + throw new CommandException(MESSAGE_PERSON_ALREADY_IN_GROUP); + } + + if (model.personIsInAGroup(personToJoin.get())) { + throw new CommandException(MESSAGE_PERSON_IN_ANOTHER_GROUP); + } + + if (groupToJoin.get().isFull()) { + throw new CommandException(MESSAGE_GROUP_FULL); + } + + // Add the person to the group + model.addPersonToGroup(personToJoin.get(), groupToJoin.get()); + + return new CommandResult(String.format(MESSAGE_JOIN_SUCCESS, + personToJoin.get().getName(), groupToJoin.get().getNumber()), + false, false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof JoinCommand)) { + return false; + } + + JoinCommand otherJoinCommand = (JoinCommand) other; + return targetEmail.equals(otherJoinCommand.targetEmail) + && targetGroupNumber == otherJoinCommand.targetGroupNumber; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetEmail", targetEmail) + .add("targetGroupNumber", targetGroupNumber) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/LeaveCommand.java b/src/main/java/seedu/address/logic/commands/LeaveCommand.java new file mode 100644 index 00000000000..05fb9c71ec3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LeaveCommand.java @@ -0,0 +1,96 @@ +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_GROUP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Email; +import seedu.address.model.person.Person; + +/** + * Removes a person specified by email from a group specified by group number. + */ +public class LeaveCommand extends Command { + + public static final String COMMAND_WORD = "leave"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes the person specified by email from the group specified by group number.\n" + + "Parameters: " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_GROUP + "GROUP NUMBER\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_EMAIL + "johnd@u.nus.edu " + + PREFIX_GROUP + "1"; + + public static final String MESSAGE_LEAVE_SUCCESS = "Leave successful! %1$s has left Group %2$s!"; + public static final String MESSAGE_LEAVE_EMAIL_NOT_FOUND = "Person with the provided email not found."; + public static final String MESSAGE_LEAVE_GROUP_NOT_FOUND = "Group with the provided group number not found."; + public static final String MESSAGE_NOT_IN_GROUP = "The above student is not a member of the provided group."; + + private final Email targetEmail; + private final int targetGroupNumber; + + /** + * Constructs a {@code LeaveCommand} with the specified email and group number to leave a group. + * + * @param targetEmail The email of the person to leave the group. + * @param targetGroupNumber The number of the group to leave. + */ + public LeaveCommand(Email targetEmail, int targetGroupNumber) { + this.targetEmail = targetEmail; + this.targetGroupNumber = targetGroupNumber; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + // Find the person with the provided email + Person personToLeave = model.getPersonWithEmail(targetEmail) + .orElseThrow(() -> new CommandException(MESSAGE_LEAVE_EMAIL_NOT_FOUND)); + + // Find the group with the provided group number + Group groupToLeave = model.getGroupWithNumber(targetGroupNumber) + .orElseThrow(() -> new CommandException(MESSAGE_LEAVE_GROUP_NOT_FOUND)); + + if (!groupToLeave.hasMember(personToLeave)) { + throw new CommandException(MESSAGE_NOT_IN_GROUP); + } + // Remove the person from the group + model.removePersonFromGroup(personToLeave, groupToLeave); + + return new CommandResult(String.format(MESSAGE_LEAVE_SUCCESS, + personToLeave.getName(), groupToLeave.getNumber()), false, false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof LeaveCommand)) { + return false; + } + + LeaveCommand otherLeaveCommand = (LeaveCommand) other; + return targetEmail.equals(otherLeaveCommand.targetEmail) + && targetGroupNumber == otherLeaveCommand.targetGroupNumber; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetEmail", targetEmail) + .add("targetGroupNumber", targetGroupNumber) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..f4e92e4b9ad 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,23 +1,25 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; 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 = "Viewing all students"; + public static final String MESSAGE_FAILURE = "Error: Unable to retrieve student entries. Please try again."; @Override public CommandResult execute(Model model) { requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); } diff --git a/src/main/java/seedu/address/logic/commands/ListGroupCommand.java b/src/main/java/seedu/address/logic/commands/ListGroupCommand.java new file mode 100644 index 00000000000..34e7d1d6d73 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListGroupCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.model.Model; + +/** + * Lists all groups in the address book to the user. + */ +public class ListGroupCommand extends Command { + + public static final String COMMAND_WORD = "listGroup"; + + public static final String MESSAGE_SUCCESS = "Viewing all groups"; + public static final String MESSAGE_FAILURE = "Error: Unable to retrieve group entries. Please try again."; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS, false, false, true, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/MarkCommand.java b/src/main/java/seedu/address/logic/commands/MarkCommand.java new file mode 100644 index 00000000000..3f92bbc72df --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MarkCommand.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Optional; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupContainsKeywordsPredicate; +import seedu.address.model.group.tasks.Task; +import seedu.address.model.group.tasks.TaskList; + +/** + * Marks a task as done within a specific group. + */ +public class MarkCommand extends Command { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task specified as done.\n" + + "Parameters: " + PREFIX_GROUP + "GROUP_NUMBER " + PREFIX_TASK_INDEX + "TASK_INDEX\n" + + "Example: " + COMMAND_WORD + " gr/2 ti/3"; + + public static final String MESSAGE_SUCCESS = "Marked task number %2$s for group %1$s"; + public static final String MESSAGE_TASK_GROUP_NOT_FOUND = "Group with the provided group number not found."; + public static final String MESSAGE_INVALID_TASK_INDEX = "Invalid task index. Task not found."; + private final int groupId; + private final int taskIndex; + private final GroupContainsKeywordsPredicate predicate; + + /** + * Creates a MarkCommand to mark a task as done within a specific group. + * + * @param groupId The group ID in which the task exists. + * @param taskIndex The index of the task to mark as done. + */ + public MarkCommand(int groupId, int taskIndex, GroupContainsKeywordsPredicate predicate) { + this.groupId = groupId; + this.taskIndex = taskIndex; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // Retrieve the group using the groupId + Optional optionalGroup = model.getGroupWithNumber(groupId); + if (optionalGroup.isEmpty()) { + throw new CommandException(MESSAGE_TASK_GROUP_NOT_FOUND); + } + Group group = optionalGroup.get(); + TaskList taskList = group.getTasks(); + if (isValidTaskIndex(taskIndex, taskList)) { + Task taskToMark = taskList.getTasks().get(taskIndex); + taskToMark.mark(); + String displayedTasks = taskList.toString(); + requireNonNull(model); + model.updateFilteredGroupList(predicate); + return new CommandResult(String.format(MESSAGE_SUCCESS, groupId, taskIndex + 1) + "\n" + displayedTasks, + false, false, true, false); + } else { + throw new CommandException(MESSAGE_INVALID_TASK_INDEX); + } + + } + + public boolean isValidTaskIndex(int taskIndex, TaskList taskList) { + return taskIndex >= 0 && taskIndex < taskList.getTasks().size(); + } + + @Override + public boolean equals(Object other) { + // Basic checks + if (other == this) { + return true; + } + if (!(other instanceof MarkCommand)) { + return false; + } + + // Property checks + MarkCommand otherMarkCommand = (MarkCommand) other; + return groupId == otherMarkCommand.groupId && taskIndex == otherMarkCommand.taskIndex; + } +} diff --git a/src/main/java/seedu/address/logic/commands/TasksCommand.java b/src/main/java/seedu/address/logic/commands/TasksCommand.java new file mode 100644 index 00000000000..246e3a33781 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TasksCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Optional; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupContainsKeywordsPredicate; +import seedu.address.model.group.exceptions.TaskException; +import seedu.address.model.group.tasks.TaskInitializer; +import seedu.address.model.group.tasks.TaskList; + +/** + * Lists out all tasks for a specific group. + */ +public class TasksCommand extends Command { + + public static final String COMMAND_WORD = "tasks"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists out all tasks for a specific group.\n" + + "Parameters: GROUP_NUMBER\n" + + "Example: " + COMMAND_WORD + " 3"; + + public static final String MESSAGE_SUCCESS = "Listing out tasks for group %1$s"; + public static final String MESSAGE_TASK_GROUP_NOT_FOUND = "Group with the provided group number not found."; + + private final int groupId; + private final GroupContainsKeywordsPredicate predicate; + + + /** + * Creates a TasksCommand to list out all tasks for a specific group. + * + * @param groupId The group ID for which tasks should be listed. + */ + public TasksCommand(int groupId, GroupContainsKeywordsPredicate predicate) { + this.groupId = groupId; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // Retrieve the group using the groupId + Optional optionalGroup = model.getGroupWithNumber(groupId); + if (optionalGroup.isEmpty()) { + throw new CommandException(MESSAGE_TASK_GROUP_NOT_FOUND); + } + + Group group = optionalGroup.get(); + + TaskList taskList = group.getTasks(); + + if (taskList.isEmpty()) { + try { + taskList = TaskInitializer.initializeTasks(); + } catch (TaskException e) { + throw new RuntimeException(e); + } + group.addTasks(taskList); + } + + String displayedTasks = taskList.toString(); + requireNonNull(model); + model.updateFilteredGroupList(predicate); + + return new CommandResult(String.format(MESSAGE_SUCCESS, groupId) + "\n" + displayedTasks, + false, false, true, false); + } + + @Override + public boolean equals(Object other) { + // basic checks + if (other == this) { + return true; + } + if (!(other instanceof TasksCommand)) { + return false; + } + + // property checks + TasksCommand otherTasksCommand = (TasksCommand) other; + return groupId == otherTasksCommand.groupId; + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnMarkCommand.java b/src/main/java/seedu/address/logic/commands/UnMarkCommand.java new file mode 100644 index 00000000000..753af8cf976 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnMarkCommand.java @@ -0,0 +1,95 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Optional; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupContainsKeywordsPredicate; +import seedu.address.model.group.tasks.Task; +import seedu.address.model.group.tasks.TaskList; + +/** + * Marks a task as not done within a specific group. + */ +public class UnMarkCommand extends Command { + + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task specified as not done.\n" + + "Parameters:" + PREFIX_GROUP + "GROUP_NUMBER " + PREFIX_TASK_INDEX + "TASK_INDEX\n" + + "Example: " + COMMAND_WORD + " gr/2 ti/3"; + + public static final String MESSAGE_SUCCESS = "Unmarked task number %2$s for group %1$s"; + public static final String MESSAGE_TASK_GROUP_NOT_FOUND = "Group with the provided group number not found."; + public static final String MESSAGE_INVALID_TASK_INDEX = "Invalid task index."; + private final int groupId; + private final int taskIndex; + private final GroupContainsKeywordsPredicate predicate; + + + /** + * Creates an UnMarkCommand to mark a task as not done within a specific group. + * + * @param groupId The group ID in which the task exists. + * @param taskIndex The index of the task to mark as not done. + */ + public UnMarkCommand(int groupId, int taskIndex, GroupContainsKeywordsPredicate predicate) { + this.groupId = groupId; + this.taskIndex = taskIndex; + this.predicate = predicate; + + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // Retrieve the group using the groupId + Optional optionalGroup = model.getGroupWithNumber(groupId); + if (optionalGroup.isEmpty()) { + throw new CommandException(MESSAGE_TASK_GROUP_NOT_FOUND); + } + Group group = optionalGroup.get(); + TaskList taskList = group.getTasks(); + if (isValidTaskIndex(taskIndex, taskList)) { + Task taskToMark = taskList.getTasks().get(taskIndex); + taskToMark.unMark(); + String displayedTasks = taskList.toString(); + requireNonNull(model); + model.updateFilteredGroupList(predicate); + return new CommandResult(String.format(MESSAGE_SUCCESS, groupId, taskIndex + 1) + "\n" + + displayedTasks, false, false, true, false); + } else { + throw new CommandException(MESSAGE_INVALID_TASK_INDEX); + } + } + + public boolean isValidTaskIndex(int taskIndex, TaskList taskList) { + return taskIndex >= 0 && taskIndex < taskList.getTasks().size(); + } + + @Override + public boolean equals(Object other) { + // basic checks + if (other == this) { + return true; + } + if (!(other instanceof UnMarkCommand)) { + return false; + } + + // Property checks + UnMarkCommand otherUnMarkCommand = (UnMarkCommand) other; + return groupId == otherUnMarkCommand.groupId && taskIndex == otherUnMarkCommand.taskIndex; + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..b8ee8935bcd 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,23 +1,31 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NATIONALITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL_MEDIA_LINK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Description; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nationality; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Year; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** * Parses input arguments and creates a new AddCommand object @@ -31,21 +39,31 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_MAJOR, PREFIX_YEAR, PREFIX_EMAIL, + PREFIX_DESCRIPTION, PREFIX_TUTORIAL, PREFIX_SOCIAL_MEDIA_LINK, PREFIX_NATIONALITY, PREFIX_GENDER); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_MAJOR, PREFIX_YEAR, PREFIX_EMAIL, + PREFIX_DESCRIPTION, PREFIX_NATIONALITY, PREFIX_GENDER) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_MAJOR, PREFIX_YEAR, + PREFIX_EMAIL, PREFIX_DESCRIPTION, PREFIX_NATIONALITY, PREFIX_GENDER); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Major major = ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get()); + Year year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).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)); + Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + Set tutorialSet = ParserUtil.parseTutorials(argMultimap.getAllValues(PREFIX_TUTORIAL)); + Set socialMediaLinkList = ParserUtil.parseSocialMediaLinks( + argMultimap.getAllValues(PREFIX_SOCIAL_MEDIA_LINK)); + Nationality nationality = ParserUtil.parseNationality(argMultimap.getValue(PREFIX_NATIONALITY).get()); + Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); - Person person = new Person(name, phone, email, address, tagList); + + Person person = new Person(name, major, year, email, description, + tutorialSet, socialMediaLinkList, nationality, gender); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..5ff056e51ee 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,14 +9,26 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.CheckCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CreateCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteGroupCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.commands.FilterGroupCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindGroupCommand; import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.JoinCommand; +import seedu.address.logic.commands.LeaveCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListGroupCommand; +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.commands.TasksCommand; +import seedu.address.logic.commands.UnMarkCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -68,15 +80,51 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FilterCommand.COMMAND_WORD: + return new FilterCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); + case CreateCommand.COMMAND_WORD: + return new CreateCommandParser().parse(arguments); + + case CheckCommand.COMMAND_WORD: + return new CheckCommandParser().parse(arguments); + + case JoinCommand.COMMAND_WORD: + return new JoinCommandParser().parse(arguments); + + case ListGroupCommand.COMMAND_WORD: + return new ListGroupCommand(); + + case FindGroupCommand.COMMAND_WORD: + return new FindGroupCommandParser().parse(arguments); + + case FilterGroupCommand.COMMAND_WORD: + return new FilterGroupCommandParser().parse(arguments); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case LeaveCommand.COMMAND_WORD: + return new LeaveCommandParser().parse(arguments); + + case TasksCommand.COMMAND_WORD: + return new TasksCommandParser().parse(arguments); + + case MarkCommand.COMMAND_WORD: + return new MarkCommandParser().parse(arguments); + + case UnMarkCommand.COMMAND_WORD: + return new UnMarkCommandParser().parse(arguments); + + case DeleteGroupCommand.COMMAND_WORD: + return new DeleteGroupCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/CheckCommandParser.java b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java new file mode 100644 index 00000000000..9ac21d5deeb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.CheckCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new CheckCommand object + */ +public class CheckCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CheckCommand + * and returns a CheckCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public CheckCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty() || !trimmedArgs.matches("^\\d+$")) { // checks o\if args is an integer + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckCommand.MESSAGE_USAGE)); + } + return new CheckCommand(Integer.parseInt(trimmedArgs), new GroupContainsKeywordsPredicate(Arrays.asList( + String.valueOf(Integer.parseInt(trimmedArgs))))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..bab4f44a929 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,15 @@ 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_MAJOR = new Prefix("m/"); + public static final Prefix PREFIX_YEAR = new Prefix("y/"); 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_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_TUTORIAL = new Prefix("t/"); + public static final Prefix PREFIX_SOCIAL_MEDIA_LINK = new Prefix("sm/"); + public static final Prefix PREFIX_NATIONALITY = new Prefix("nt/"); + public static final Prefix PREFIX_GENDER = new Prefix("g/"); + public static final Prefix PREFIX_GROUP = new Prefix("gr/"); + public static final Prefix PREFIX_TASK_INDEX = new Prefix("ti/"); } diff --git a/src/main/java/seedu/address/logic/parser/CreateCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateCommandParser.java new file mode 100644 index 00000000000..4ee42d43118 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CreateCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.CreateCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorial.Tutorial; + +/** + * Parses input arguments and creates a new CreateCommand object + */ +public class CreateCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateCommand + * and returns a CreateCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public CreateCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL); + + if (!arePrefixesPresent(argMultimap, PREFIX_TUTORIAL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TUTORIAL); + + try { + Tutorial tutorial = ParserUtil.parseTutorial(argMultimap.getValue(PREFIX_TUTORIAL).get()); + return new CreateCommand(tutorial); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Tutorial.MESSAGE_CONSTRAINTS + "\n" + CreateCommand.MESSAGE_USAGE), pe); + } + } + + /** + * 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/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..5acfe7ca5ee 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -2,9 +2,9 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Email; /** * Parses input arguments and creates a new DeleteCommand object @@ -14,16 +14,16 @@ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + Email email = ParserUtil.parseEmail(args); + return new DeleteCommand(email); } catch (ParseException pe) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); } } - } diff --git a/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java new file mode 100644 index 00000000000..c241a5ddb00 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; + +import seedu.address.logic.commands.DeleteGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteGroupCommand object. + */ +public class DeleteGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGroupCommand + * and returns a DeleteGroupCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public DeleteGroupCommand parse(String args) throws ParseException { + requireNonNull(args); + // Parse the group number from the args using the group prefix + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUP); + + if (!argMultimap.getValue(PREFIX_GROUP).isPresent() || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE)); + } + + // Extract the group number from the argument map + try { + int groupNumber = ParserUtil.parseGroupNumber(argMultimap.getValue(PREFIX_GROUP).get()); + return new DeleteGroupCommand(groupNumber); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 46b3309a78b..15489c5bc3e 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,22 +2,27 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MAJOR; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NATIONALITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL_MEDIA_LINK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; 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.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Email; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** * Parses input arguments and creates a new EditCommand object @@ -32,54 +37,94 @@ 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_MAJOR, PREFIX_YEAR, PREFIX_EMAIL, + PREFIX_DESCRIPTION, PREFIX_TUTORIAL, PREFIX_SOCIAL_MEDIA_LINK, PREFIX_NATIONALITY, PREFIX_GENDER); - Index index; + Email email; try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); + email = ParserUtil.parseEmail(argMultimap.getPreamble()); } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_MAJOR, PREFIX_YEAR, PREFIX_EMAIL, + PREFIX_DESCRIPTION, PREFIX_NATIONALITY, PREFIX_GENDER); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.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_MAJOR).isPresent()) { + editPersonDescriptor.setMajor(ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR).get())); + } + if (argMultimap.getValue(PREFIX_YEAR).isPresent()) { + editPersonDescriptor.setYear(ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editPersonDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + /* + List tutorialsStrings = argMultimap.getAllValues(PREFIX_TUTORIAL); + if (!tutorialsStrings.isEmpty()) { + Set tutorialList = ParserUtil.parseTutorials(tutorialsStrings); + editPersonDescriptor.setTutorials(tutorialList); + } + */ + parseTutorialsForEdit(argMultimap.getAllValues(PREFIX_TUTORIAL)) + .ifPresent(editPersonDescriptor::setTutorials); + + parseSocialMediaLinksForEdit(argMultimap.getAllValues(PREFIX_SOCIAL_MEDIA_LINK)) + .ifPresent(editPersonDescriptor::setSocialMediaLinks); + + if (argMultimap.getValue(PREFIX_NATIONALITY).isPresent()) { + editPersonDescriptor.setNationality( + ParserUtil.parseNationality(argMultimap.getValue(PREFIX_NATIONALITY).get())); + } + + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(email, editPersonDescriptor); + } + + private Optional> parseTutorialsForEdit(Collection tutorials) throws ParseException { + assert tutorials != null; + + if (tutorials.isEmpty()) { + return Optional.empty(); + } + Collection tutorialSet = + tutorials.size() == 1 && tutorials.contains("") ? Collections.emptySet() : tutorials; + return Optional.of(ParserUtil.parseTutorials(tutorialSet)); } /** - * 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 socialMediaLinks} into a {@code Set} + * if {@code socialMediaLinks} is non-empty. + * If {@code socialMediaLinks} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; + private Optional> parseSocialMediaLinksForEdit(Collection socialMediaLinks) + throws ParseException { + assert socialMediaLinks != null; - if (tags.isEmpty()) { + if (socialMediaLinks.isEmpty()) { return Optional.empty(); } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + Collection socialMediaLinkSet = socialMediaLinks.size() == 1 && socialMediaLinks.contains("") + ? Collections.emptySet() : socialMediaLinks; + return Optional.of(ParserUtil.parseSocialMediaLinks(socialMediaLinkSet)); } } diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java new file mode 100644 index 00000000000..1fb8389e328 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tutorial.TutorialContainsSlotsPredicate; + +/** + * Parses input arguments and creates a new FilterCommand object + */ +public class FilterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterCommand + * and returns a FilterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + String[] tutorialSlots = trimmedArgs.split("\\s+"); + for (String tut : tutorialSlots) { + ParserUtil.parseTutorial(tut); + } + + return new FilterCommand(new TutorialContainsSlotsPredicate(Arrays.asList(tutorialSlots))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FilterGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterGroupCommandParser.java new file mode 100644 index 00000000000..eb72e23d1dd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterGroupCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.FilterGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupBelongsTutorialPredicate; + +/** + * Parses input arguments and creates a new FilterGroupCommand object + */ +public class FilterGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterGroupCommand + * and returns a FilterGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterGroupCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterGroupCommand.MESSAGE_USAGE)); + } + + ParserUtil.parseTutorial(trimmedArgs); + + return new FilterGroupCommand(new GroupBelongsTutorialPredicate(trimmedArgs)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java new file mode 100644 index 00000000000..a36b268bdb3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindGroupCommand object + */ +public class FindGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindGroupCommand + * and returns a FindGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindGroupCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindGroupCommand.MESSAGE_USAGE)); + } + + String[] groupKeywords = trimmedArgs.split("\\s+"); + for (String gk : groupKeywords) { + ParserUtil.parseGroupNumber(gk); + } + + return new FindGroupCommand(new GroupContainsKeywordsPredicate(Arrays.asList(groupKeywords))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/JoinCommandParser.java b/src/main/java/seedu/address/logic/parser/JoinCommandParser.java new file mode 100644 index 00000000000..2529530cf44 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/JoinCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.JoinCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Email; + +/** + * Parses input arguments and creates a new JoinCommand object + */ +public class JoinCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the JoinCommand + * and returns a JoinCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public JoinCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_EMAIL, PREFIX_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_EMAIL, PREFIX_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, JoinCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_EMAIL, PREFIX_GROUP); + + try { + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + int groupNumber = ParserUtil.parseGroupNumber(argMultimap.getValue(PREFIX_GROUP).get()); + return new JoinCommand(email, groupNumber); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, JoinCommand.MESSAGE_USAGE), pe); + } + } + + /** + * 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/LeaveCommandParser.java b/src/main/java/seedu/address/logic/parser/LeaveCommandParser.java new file mode 100644 index 00000000000..836bb5262dc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LeaveCommandParser.java @@ -0,0 +1,56 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.LeaveCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Email; + + +/** + * Parses input arguments and creates a new LeaveCommand object + */ +public class LeaveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the LeaveCommand + * and returns a LeaveCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public LeaveCommand parse(String args) throws ParseException { + // Similar to JoinCommandParser, parse email and group number from args + // Example: "e/johnd@u.nus.edu gr/1" + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_EMAIL, PREFIX_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_EMAIL, PREFIX_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LeaveCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_EMAIL, PREFIX_GROUP); + + try { + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + int groupNumber = ParserUtil.parseGroupNumber(argMultimap.getValue(PREFIX_GROUP).get()); + return new LeaveCommand(email, groupNumber); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, LeaveCommand.MESSAGE_USAGE), pe); + } + } + + /** + * 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/MarkCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java new file mode 100644 index 00000000000..71006336e69 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new MarkCommand object + */ +public class MarkCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the MarkCommand + * and returns a MarkCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public MarkCommand parse(String args) throws ParseException { + requireNonNull(args); + try { + String[] argParts = args.trim().split(" "); + int groupNumber = -1; + int taskIndex = -1; + + for (String part : argParts) { + if (part.startsWith("gr/")) { + groupNumber = ParserUtil.parseGroupNumber(part.substring(3)); + } else if (part.startsWith("ti/")) { + taskIndex = ParserUtil.parseTaskIndex(part.substring(3)) - 1; + } + } + + if (groupNumber == -1 || taskIndex == -1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + } + return new MarkCommand(groupNumber, taskIndex, new GroupContainsKeywordsPredicate(Arrays.asList( + String.valueOf(groupNumber)))); + } catch (NumberFormatException nfe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE), nfe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..3824d4b2101 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -6,34 +6,24 @@ import java.util.HashSet; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.group.Group; +import seedu.address.model.person.Description; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Nationality; +import seedu.address.model.person.Year; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** * 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. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } + public static final String MESSAGE_INVALID_GROUP_NUMBER = "Group number is not a non-zero unsigned integer."; /** * Parses a {@code String name} into a {@code Name}. @@ -51,74 +41,187 @@ public static Name parseName(String name) throws ParseException { } /** - * Parses a {@code String phone} into a {@code Phone}. + * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code phone} is invalid. + * @throws ParseException if the given {@code email} 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 Email parseEmail(String email) throws ParseException { + requireNonNull(email); + String trimmedEmail = email.trim(); + if (!Email.isValidEmail(trimmedEmail)) { + throw new ParseException(Email.MESSAGE_CONSTRAINTS); } - return new Phone(trimmedPhone); + return new Email(trimmedEmail); } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String major} into a {@code Major}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code major} is invalid. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); + public static Major parseMajor(String major) throws ParseException { + requireNonNull(major); + String trimmedMajor = major.trim(); + if (!Major.isValidMajor(trimmedMajor)) { + throw new ParseException(Major.MESSAGE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Major(trimmedMajor); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String year} into a {@code Year}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code year} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + public static Year parseYear(String year) throws ParseException { + requireNonNull(year); + String trimmedYear = year.trim(); + if (!Year.isValidYear(trimmedYear)) { + throw new ParseException(Year.MESSAGE_CONSTRAINTS); } - return new Email(trimmedEmail); + return new Year(trimmedYear); + } + + /** + * Parses a {@code String description} into a {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code description} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String tutorial} into a {@code Tutorial}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tutorial} is invalid. + */ + public static Tutorial parseTutorial(String tutorial) throws ParseException { + requireNonNull(tutorial); + String trimmedTutorial = tutorial.trim(); + if (!Tutorial.isValidTutorial(trimmedTutorial)) { + throw new ParseException(Tutorial.MESSAGE_CONSTRAINTS); + } + return new Tutorial(trimmedTutorial); + } + + /** + * Parses {@code Collection tutorials} into a {@code Set}. + */ + public static Set parseTutorials(Collection tutorials) throws ParseException { + requireNonNull(tutorials); + final Set tutorialSet = new HashSet<>(); + for (String tut : tutorials) { + tutorialSet.add(parseTutorial(tut)); + } + return tutorialSet; + } + + /** + * Parses a {@code String socialMediaLink} into a {@code SocialMediaLink}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code socialMediaLink} is invalid. + */ + public static SocialMediaLink parseSocialMediaLink(String socialMediaLink) throws ParseException { + requireNonNull(socialMediaLink); + String trimmedSocialMediaLink = socialMediaLink.trim(); + if (!SocialMediaLink.isValidSocialMediaLink(trimmedSocialMediaLink)) { + throw new ParseException(SocialMediaLink.MESSAGE_CONSTRAINTS); + } + return new SocialMediaLink(trimmedSocialMediaLink); + } + + /** + * Parses a collection of social media links into a set of SocialMediaLink objects. + * + * @param socialMediaLinks A collection of social media links as a Collection of strings. + * @return A Set of SocialMediaLink objects representing the parsed social media links. + * @throws ParseException If there is an issue parsing the social media links. + */ + public static Set parseSocialMediaLinks(Collection socialMediaLinks) + throws ParseException { + requireNonNull(socialMediaLinks); + final Set socialMediaLinkSet = new HashSet<>(); + for (String socialMedia : socialMediaLinks) { + socialMediaLinkSet.add(parseSocialMediaLink(socialMedia)); + } + return socialMediaLinkSet; } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String nationality} into a {@code Nationality}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @param nationality A string representing the nationality. + * @return A {@code Nationality} object. + * @throws ParseException If the given {@code nationality} 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 Nationality parseNationality(String nationality) throws ParseException { + requireNonNull(nationality); + String trimmedNationality = nationality.trim(); + try { + return new Nationality(trimmedNationality); + } catch (IllegalArgumentException e) { + throw new ParseException(Nationality.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String gender} into a {@code Gender}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code gender} is invalid. + */ + public static Gender parseGender(String gender) throws ParseException { + requireNonNull(gender); + String trimmedGender = gender.trim().toLowerCase(); // Convert to lowercase for case-insensitive check + if (!Gender.isValidGender(trimmedGender)) { + throw new ParseException(Gender.MESSAGE_CONSTRAINTS); + } + return new Gender(trimmedGender); + } + + /** + * Parses {@code groupNumber} into an {@code Integer} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws ParseException if the specified group number is invalid (not non-zero unsigned integer). + */ + public static int parseGroupNumber(String groupNumber) throws ParseException { + String trimmedIndex = groupNumber.trim(); + if (!Group.isValidGroupNumber(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_GROUP_NUMBER); + } + return Integer.parseInt(trimmedIndex); + } + + /** + * Parses a string into an integer task index. + * + * @param taskIndexString The string representation of the task index. + * @return The parsed integer task index. + * @throws ParseException if the specified task index is invalid (not a positive integer). */ - 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 int parseTaskIndex(String taskIndexString) throws ParseException { + String trimmedIndex = taskIndexString.trim(); + try { + int taskIndex = Integer.parseInt(trimmedIndex); + if (taskIndex <= 0) { + throw new ParseException("Task index must be a positive integer."); + } + return taskIndex; + } catch (NumberFormatException e) { + throw new ParseException("Invalid task index format."); } - return tagSet; } } diff --git a/src/main/java/seedu/address/logic/parser/TasksCommandParser.java b/src/main/java/seedu/address/logic/parser/TasksCommandParser.java new file mode 100644 index 00000000000..16b44d2efec --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TasksCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.TasksCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new TasksCommand object + */ +public class TasksCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TasksCommand + * and returns a TasksCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public TasksCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + + // Check if the input is an unsigned, non-zero integer + if (!trimmedArgs.matches("\\d+") || trimmedArgs.equals("0")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TasksCommand.MESSAGE_USAGE)); + } + + try { + int groupNumber = ParserUtil.parseGroupNumber(trimmedArgs); + return new TasksCommand(groupNumber, new GroupContainsKeywordsPredicate(Arrays.asList( + String.valueOf(groupNumber)))); + } catch (NumberFormatException nfe) { + // This should not happen as we have already validated the input + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TasksCommand.MESSAGE_USAGE), nfe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnMarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UnMarkCommandParser.java new file mode 100644 index 00000000000..0fec2b387c2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnMarkCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.UnMarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new UnMarkCommand object + */ +public class UnMarkCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnMarkCommand + * and returns a UnMarkCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public UnMarkCommand parse(String args) throws ParseException { + requireNonNull(args); + try { + String[] argParts = args.trim().split(" "); + int groupNumber = -1; + int taskIndex = -1; + + for (String part : argParts) { + if (part.startsWith("gr/")) { + groupNumber = ParserUtil.parseGroupNumber(part.substring(3)); + } else if (part.startsWith("ti/")) { + taskIndex = ParserUtil.parseTaskIndex(part.substring(3)) - 1; + } + } + + if (groupNumber == -1 || taskIndex == -1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnMarkCommand.MESSAGE_USAGE)); + } + return new UnMarkCommand(groupNumber, taskIndex, new GroupContainsKeywordsPredicate(Arrays.asList( + String.valueOf(groupNumber)))); + } catch (NumberFormatException nfe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnMarkCommand.MESSAGE_USAGE), nfe); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..ebac319170c 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -6,6 +6,8 @@ import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.group.Group; +import seedu.address.model.group.UniqueGroupList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -16,6 +18,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueGroupList groups; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,12 +29,13 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + groups = new UniqueGroupList(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates an AddressBook using the Persons and Groups in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -48,6 +52,14 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the group list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setGroups(List groups) { + this.groups.setGroups(groups); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -55,6 +67,7 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setGroups(newData.getGroupList()); } //// person-level operations @@ -94,6 +107,37 @@ public void removePerson(Person key) { persons.remove(key); } + /** + * Returns true if a group with the same identity as {@code group} exists in the address book. + */ + public boolean hasGroup(Group group) { + requireNonNull(group); + return groups.contains(group); + } + + /** + * Adds a group to the address book. + * + * @param group + */ + public void addGroup(Group group) { + requireNonNull(group); + groups.add(group); + } + + /** + * Adds the given {@code Person} to the give {@code Group}. + * + * @param person The person to be added. + * @param group The group that the person will be added to. + */ + public void addPersonToGroup(Person person, Group group) { + requireNonNull(person); + requireNonNull(group); + + group.addMember(person); + } + //// util methods @Override @@ -108,6 +152,17 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getGroupList() { + return groups.asUnmodifiableObservableList(); + } + + @Override + public void sortGroups() { + List sortedGroups = groups.getSortedList(); + this.setGroups(sortedGroups); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -127,4 +182,25 @@ public boolean equals(Object other) { public int hashCode() { return persons.hashCode(); } + + + + /** + * Removes the given person from the specified group. + * @param person The person to be removed from the group. + */ + public void removePersonFromGroup(Person person) { + groups.remove(person); + } + + /** + * Removes the given group from the address book. + * + * @param group The group to be removed. + */ + public void removeGroup(Group group) { + requireNonNull(group); + groups.remove(group); + } + } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..5b839de1b54 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,10 +1,14 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.group.Group; +import seedu.address.model.group.tasks.TaskList; +import seedu.address.model.person.Email; import seedu.address.model.person.Person; /** @@ -14,6 +18,9 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_GROUPS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -53,26 +60,26 @@ 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 person with the same identity as {@code person} exists in StudentConnect. */ boolean hasPerson(Person person); /** * Deletes the given person. - * The person must exist in the address book. + * The person must exist in StudentConnect. */ void deletePerson(Person target); /** * Adds the given person. - * {@code person} must not already exist in the address book. + * {@code person} must not already exist in StudentConnect. */ void addPerson(Person person); /** * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * {@code target} must exist in StudentConnect. + * The person identity of {@code editedPerson} must not be the same as another existing person in StudentConnect. */ void setPerson(Person target, Person editedPerson); @@ -84,4 +91,84 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Adds the given group. + */ + void addGroup(Group group); + + /** + * Adds the given {@code Person} to the give {@code Group}. + * + * @param person The person to be added. + * @param group The group that the person will be added to. + */ + void addPersonToGroup(Person person, Group group); + + /** Returns an unmodifiable view of the filtered group list */ + ObservableList getFilteredGroupList(); + + /** + * Updates the filter of the filtered group list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredGroupList(Predicate predicate); + + /** + * Retrieves a {@code Person} with the specified email. + * + * @param email The email associated with the person. + * @return An {@code Optional} containing the {@code Person} if found, or empty otherwise. + */ + Optional getPersonWithEmail(Email email); + + /** + * Retrieves a {@code Group} with the specified group number. + * + * @param number The number associated with the group. + * @return An {@code Optional} containing the {@code Group} if found, or empty otherwise. + */ + Optional getGroupWithNumber(int number); + + /** + * Returns true if the given person is in a group. + * + * @param person The person to be checked. + */ + boolean personIsInAGroup(Person person); + + /** + * Returns the group that the given person is in. + * + * @param person + */ + Group getGroupThatPersonIsIn(Person person); + + /** + * Removes a person from a group. + * + * @param person The person to be removed from the group. + * @param group The group from which the person should be removed. + */ + void removePersonFromGroup(Person person, Group group); + + /** + * Adds the given {@code TaskList} to the give {@code Group}. + * + * @param taskList The taskList to be added. + * @param group The group that the task will be added to. + */ + void addTasksToGroup(TaskList taskList, Group group); + + /** + * Returns true if a group with the same identity as {@code group} exists in StudentConnect. + */ + boolean hasGroup(Group group); + + /** + * Deletes the given group. + * The group must exist in StudentConnect. + */ + void deleteGroup(Group group); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..4dbec81573c 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,7 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; @@ -11,6 +12,9 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.group.Group; +import seedu.address.model.group.tasks.TaskList; +import seedu.address.model.person.Email; import seedu.address.model.person.Person; /** @@ -22,9 +26,14 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredGroups; + /** * Initializes a ModelManager with the given addressBook and userPrefs. + * + * @param addressBook The initial address book data. + * @param userPrefs The user preferences. */ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { requireAllNonNull(addressBook, userPrefs); @@ -34,8 +43,12 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredGroups = new FilteredList<>(this.addressBook.getGroupList()); } + /** + * Default constructor for ModelManager. + */ public ModelManager() { this(new AddressBook(), new UserPrefs()); } @@ -111,6 +124,68 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public void addGroup(Group group) { + addressBook.addGroup(group); + updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS); + } + + @Override + public void addPersonToGroup(Person person, Group group) { + addressBook.addPersonToGroup(person, group); + } + + @Override + public boolean personIsInAGroup(Person person) { + for (Group group : addressBook.getGroupList()) { + if (group.hasMember(person)) { + return true; + } + } + return false; + } + + @Override + public Group getGroupThatPersonIsIn(Person person) { + assert personIsInAGroup(person) : "This person is not in a group."; + for (Group group : addressBook.getGroupList()) { + if (group.hasMember(person)) { + return group; + } + } + return null; + } + + @Override + public void removePersonFromGroup(Person person, Group group) { + requireAllNonNull(person, group); + addressBook.removePersonFromGroup(person); + } + + /** + * Checks if the specified group exists in the address book. + * + * @param group The group to check for existence. + * @return True if the group exists, false otherwise. + * @throws NullPointerException if the given group is null. + */ + public boolean hasGroup(Group group) { + requireNonNull(group); + return addressBook.hasGroup(group); + } + + @Override + public void addTasksToGroup(TaskList taskList, Group group) { + requireAllNonNull(taskList, group); + group.addTasks(taskList); + } + + @Override + public void deleteGroup(Group group) { + requireNonNull(group); + addressBook.removeGroup(group); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -128,6 +203,23 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Filtered Group List Accessors ============================================================== + + /** + * Returns an unmodifiable view of the list of {@code Group} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredGroupList() { + return filteredGroups; + } + + @Override + public void updateFilteredGroupList(Predicate predicate) { + requireNonNull(predicate); + filteredGroups.setPredicate(predicate); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -142,7 +234,34 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredPersons.equals(otherModelManager.filteredPersons) + && filteredGroups.equals(otherModelManager.filteredGroups); + } + + @Override + public Optional getPersonWithEmail(Email email) { + requireNonNull(email); + + // Iterate through the filtered list of persons + for (Person person : filteredPersons) { + if (person.getEmail().equals(email)) { + return Optional.of(person); + } + } + + return Optional.empty(); + } + + @Override + public Optional getGroupWithNumber(int number) { + // Iterate through the filtered list of groups + for (Group group : filteredGroups) { + if (group.getNumber() == number) { + return Optional.of(group); + } + } + + return Optional.empty(); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..403d063af9d 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -14,4 +15,14 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the groups list. + * This list will not contain any duplicate groups. + */ + ObservableList getGroupList(); + + /** + * Sorts the address book's groups by group number. + */ + public void sortGroups(); } diff --git a/src/main/java/seedu/address/model/group/Group.java b/src/main/java/seedu/address/model/group/Group.java new file mode 100644 index 00000000000..8baa849a9af --- /dev/null +++ b/src/main/java/seedu/address/model/group/Group.java @@ -0,0 +1,171 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.group.exceptions.TaskException; +import seedu.address.model.group.tasks.TaskInitializer; +import seedu.address.model.group.tasks.TaskList; +import seedu.address.model.person.Person; +import seedu.address.model.tutorial.Tutorial; + +/** + * Represents a Group in StudentConnect. + * Guarantees: details are present and not null, number is immutable. + */ +public class Group { + + private static final int MAXIMUM_SIZE = 5; + private final int number; + private final Tutorial tutorial; + private Set members = new HashSet<>(); + private TaskList tasks = new TaskList(); + + /** + * Constructs a {@code Group}. + * + * @param number A valid group number. + * @param tutorial A valid tutorial group. + */ + public Group(int number, Tutorial tutorial) { + this.number = number; + this.tutorial = tutorial; + if (tasks.isEmpty()) { + try { + tasks = TaskInitializer.initializeTasks(); + } catch (TaskException e) { + throw new RuntimeException(e); + } + } + + } + + /** + * Constructs a {@code Group}. + * + * @param number A valid group number. + * @param tutorial A valid tutorial group. + * @param members The members of the group. + * @param tasks The initial tasks for the group. + */ + public Group(int number, Tutorial tutorial, Set members, TaskList tasks) { + this.number = number; + this.tutorial = tutorial; + this.members = members; + this.tasks = tasks; + } + + /** + * Adds a person to the group. + * + * @param person The person to be added as a member. + */ + public void addMember(Person person) { + this.members.add(person); + } + + public int getNumber() { + return this.number; + } + + public Tutorial getTutorial() { + return this.tutorial; + } + + public Set getMembers() { + return Collections.unmodifiableSet(members); + } + + /** + * Returns true if the set contains an equivalent person as the given argument. + */ + public boolean hasMember(Person person) { + return this.members.stream().anyMatch(person::isSamePerson); + } + + /** + * Returns true if the group contains the maximum number of members. + */ + public boolean isFull() { + return this.members.size() == MAXIMUM_SIZE; + } + + /** + * Returns true if the group number is a positive integer. + */ + public static boolean isValidGroupNumber(String groupNumber) { + requireNonNull(groupNumber); + + try { + int value = Integer.parseInt(groupNumber); + return value > 0 && !groupNumber.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String) + } catch (NumberFormatException nfe) { + return false; + } + } + + /** + * Returns true if both groups have the same number. + * This defines a weaker notion of equality between two groups. + */ + public boolean isSameGroup(Group otherGroup) { + if (otherGroup == this) { + return true; + } + + return otherGroup != null + && otherGroup.getNumber() == this.number; + } + + public TaskList getTasks() { + return tasks; + } + + public void addTasks(TaskList tasks) { + this.tasks.addTasks(tasks.getTasks()); + } + + /** + * Returns true if both groups have the same number, tutorial and members. + * This defines a stronger notion of equality between two groups. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Group)) { + return false; + } + + Group otherGroup = (Group) other; + return number == otherGroup.getNumber() + && tutorial.equals(otherGroup.tutorial) + && members.equals(otherGroup.members); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("group number", number) + .add("tutorial", tutorial) + .add("members", members) + .add("tasks", tasks) + .toString(); + } + + /** + * Removes a person from the group. + * @param person The person to be removed. + * @return True if the person was removed from the group, false if the person was not found in the group. + */ + public boolean removeMember(Person person) { + return members.remove(person); + } +} diff --git a/src/main/java/seedu/address/model/group/GroupBelongsTutorialPredicate.java b/src/main/java/seedu/address/model/group/GroupBelongsTutorialPredicate.java new file mode 100644 index 00000000000..4242085764e --- /dev/null +++ b/src/main/java/seedu/address/model/group/GroupBelongsTutorialPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.group; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Group}'s {@code Tutorial} matches the tutorial given. + */ +public class GroupBelongsTutorialPredicate implements Predicate { + private final String tutorial; + + public GroupBelongsTutorialPredicate(String tutorial) { + this.tutorial = tutorial; + } + + @Override + public boolean test(Group group) { + String grpTutorial = group.getTutorial().value; + return StringUtil.containsTutorial(grpTutorial, tutorial); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupBelongsTutorialPredicate)) { + return false; + } + + GroupBelongsTutorialPredicate otherGroupBelongsTutorialPredicate = (GroupBelongsTutorialPredicate) other; + return tutorial.equals(otherGroupBelongsTutorialPredicate.tutorial); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("tutorial", tutorial).toString(); + } +} diff --git a/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java new file mode 100644 index 00000000000..38fb4473a94 --- /dev/null +++ b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.group; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Group}'s {@code Number} matches any of the keywords given. + */ +public class GroupContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public GroupContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Group group) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(String.valueOf(new Integer(group.getNumber())), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupContainsKeywordsPredicate)) { + return false; + } + + GroupContainsKeywordsPredicate otherGroupContainsKeywordsPredicate = (GroupContainsKeywordsPredicate) other; + return keywords.equals(otherGroupContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/group/UniqueGroupList.java b/src/main/java/seedu/address/model/group/UniqueGroupList.java new file mode 100644 index 00000000000..f450f91f478 --- /dev/null +++ b/src/main/java/seedu/address/model/group/UniqueGroupList.java @@ -0,0 +1,158 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.group.exceptions.DuplicateGroupException; +import seedu.address.model.group.exceptions.GroupNotFoundException; +import seedu.address.model.person.Person; + +/** + * A list of groups that enforces uniqueness between its elements and does not allow nulls. + * A group is considered unique by comparing using {@code Group#isSameGroup(Group)}. As such, adding and updating of + * groups uses Group#isSameGroup(Group) for equality so as to ensure that the group being added or updated is + * unique in terms of identity in the UniqueGroupList. However, the removal of a group uses Group#equals(Object) so + * as to ensure that the group with exactly the same members will be removed. + * + * Supports a minimal set of list operations. + * + * @see Group#isSameGroup(Group) + */ +public class UniqueGroupList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent group as the given argument. + */ + public boolean contains(Group toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameGroup); + } + + /** + * Adds a group to the list. + * The group must not already exist in the list. + */ + public void add(Group toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGroupException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent group from the list. + * The group must exist in the list. + */ + public void remove(Group toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new GroupNotFoundException(); + } + } + + /** + * Removes the specified person from all groups in the list. + */ + public void remove(Person person) { + requireNonNull(person); + + for (Group group : internalList) { + if (group.hasMember(person)) { + group.removeMember(person); + } + } + } + + + public void setGroups(UniqueGroupList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setGroups(List groups) { + requireAllNonNull(groups); + if (!groupsAreUnique(groups)) { + throw new DuplicateGroupException(); + } + + internalList.setAll(groups); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Returns the list of {@code groups}, sorted by group number. + */ + public List getSortedList() { + List sortedList = internalList.stream() + .sorted(Comparator.comparing(Group::getNumber)) + .collect(Collectors.toList()); + + return sortedList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueGroupList)) { + return false; + } + + UniqueGroupList otherUniqueGroupList = (UniqueGroupList) other; + return internalList.equals(otherUniqueGroupList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code groups} contains only unique groups. + */ + private boolean groupsAreUnique(List groups) { + for (int i = 0; i < groups.size() - 1; i++) { + for (int j = i + 1; j < groups.size(); j++) { + if (groups.get(i).isSameGroup(groups.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java new file mode 100644 index 00000000000..20292d5c11d --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java @@ -0,0 +1,11 @@ +package seedu.address.model.group.exceptions; + +/** + * Signals that the operation will result in duplicate Groups (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateGroupException extends RuntimeException { + public DuplicateGroupException() { + super("Operation would result in duplicate groups"); + } +} diff --git a/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java new file mode 100644 index 00000000000..e376cb1e7a0 --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java @@ -0,0 +1,11 @@ +package seedu.address.model.group.exceptions; + +/** + * Signals that the operation is looking for a group that does not exist. + */ + +public class GroupNotFoundException extends RuntimeException { + public GroupNotFoundException() { + super("Group not found."); + } +} diff --git a/src/main/java/seedu/address/model/group/exceptions/TaskException.java b/src/main/java/seedu/address/model/group/exceptions/TaskException.java new file mode 100644 index 00000000000..2c20b3f342c --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/TaskException.java @@ -0,0 +1,19 @@ +package seedu.address.model.group.exceptions; + +/** + * The `TaskException` class represents an exception specific to the Duke program. + * It is a subclass of the standard Java `Exception` class and is used + * to handle custom error messages. + */ +public class TaskException extends Exception { + + /** + * Initializes a new instance of `TaskException` with the specified error message. + * + * @param message The error message associated with this exception. + */ + public TaskException(String message) { + super(message); + } +} + diff --git a/src/main/java/seedu/address/model/group/tasks/Deadline.java b/src/main/java/seedu/address/model/group/tasks/Deadline.java new file mode 100644 index 00000000000..aa817f77ec0 --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/Deadline.java @@ -0,0 +1,40 @@ +package seedu.address.model.group.tasks; + +import java.time.LocalDateTime; + +import seedu.address.model.group.exceptions.TaskException; + +/** + * The `Deadline` class represents a task with a specific deadline in the Duke program. + * It is a subclass of the `Task` class and provides functionality to handle tasks with deadlines. + */ +public class Deadline extends Task { + + private String byStr; + private LocalDateTime by; + + /** + * Initializes a new `Deadline` task with the specified description, status, module, and deadline. + * + * @param task The description of the task. + * @param status The status of the task (complete or incomplete). + * @param module The module the task is assigned to (CS2103T or CS2101). + * @param by The deadline of the task in string format (dd/MM/yyyy). + * @throws TaskException If there is an issue parsing the deadline format. + */ + public Deadline(String task, TaskStatus status, TaskModule module, String by) throws TaskException { + super(task, status, module, "D", by); + try { + this.by = parseDateTime(by); + this.byStr = by; + } catch (Exception e) { + throw new TaskException("Invalid date format :< Please use dd/MM/yyyy\n"); + } + } + + @Override + public String getDeadline() { + return "(by " + this.byStr + ")"; + } + +} diff --git a/src/main/java/seedu/address/model/group/tasks/Task.java b/src/main/java/seedu/address/model/group/tasks/Task.java new file mode 100644 index 00000000000..452c86b6346 --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/Task.java @@ -0,0 +1,215 @@ +package seedu.address.model.group.tasks; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + + +/** + * The Task class represents a task in the StudentConnect application. + */ +public class Task { + + private String task; + private TaskStatus status; + private TaskModule module; + private String type; + private String by; + + /** + * Constructs a Task object with a task description, status, module, and save status. + * + * @param task The description of the task. + * @param status The status of the task (complete or incomplete). + * @param module The module the task is assigned to (CS2103T or CS2101). + * @param type The type of task, either a deadline or a todo. + * @param by The date the task must be completed by, empty for a todo. + */ + public Task(String task, TaskStatus status, TaskModule module, String type, String by) { + this.task = task; + this.status = status; + this.module = module; + this.type = type; + this.by = by; + + if (!task.isEmpty()) { + addTask(this.task); + } + } + + /** + * Empty constructor for Task. + */ + public Task() { + + } + + /** + * Returns a string representation of the task. + * + * @return A string representing the task's status and description. + */ + @Override + public String toString() { + return this.type + " " + status.toString() + " " + this.module + " " + this.task + " " + this.by; + } + + /** + * Adds a task to the list of tasks. + * + * @param task The task description to add. + */ + public void addTask(String task) { + } + + /** + * Gets the status of the task. + * + * @return The status of the task. + */ + public TaskStatus getStatus() { + return this.status; + } + + /** + * Sets the status of the task. + * + * @param taskStatus The status to set. + */ + public void setStatus(TaskStatus taskStatus) { + this.status = taskStatus; + } + + /** + * Gets the module of the task. + * + * @return The module the task is assigned to. + */ + public TaskModule getModule() { + return module; + } + + /** + * Gets the description of the task. + * + * @return The task description. + */ + public String getTask() { + return this.task; + } + + /** + * Gets the type of the task. + * + * @return The task type. + */ + public String getType() { + return this.type; + } + + /** + * Gets the deadline of the task. + * + * @return The task deadline. + */ + public String getBy() { + return this.by; + } + + /** + * Parses a date and time string to a LocalDateTime object. + * + * @param dateTimeString The date and time string in the format "dd/MM/yyyy HHmm". + * @return A LocalDateTime object representing the parsed date and time. + */ + public LocalDateTime parseDateTime(String dateTimeString) { + // Split the input string into date and time parts + String[] parts = dateTimeString.split(" ", 2); + + // Check if there are exactly two parts (date and time) + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid date/time format: " + + dateTimeString); + } + + String datePart = parts[0]; + String timePart = parts[1]; + + // Define a formatter for the date part, e.g., "dd/MM/yyyy" + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + // Parse the date part into a LocalDate object + LocalDate date = LocalDate.parse(datePart, dateFormatter); + + // Define a formatter for the time part, e.g., "HHmm" + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmm"); + // Parse the time part into a LocalTime object + LocalTime time = LocalTime.parse(timePart, timeFormatter); + + // Combine the date and time into a LocalDateTime object + return LocalDateTime.of(date, time); + } + + /** + * Gets the type of the task. + * + * @return The string representing task type. + */ + public String getTaskType() { + // Your logic to determine the task type based on the instance's actual class + if (this instanceof Todo) { + return "T"; + } else if (this instanceof Deadline) { + return "D"; + } else { + return ""; // Handle unknown task types or add appropriate logic + } + } + + /** + * Marks the task as done. + */ + public void mark() { + this.status = TaskStatus.DONE; + } + + /** + * Marks the task as not done. + */ + public void unMark() { + this.status = TaskStatus.NOT_DONE; + } + + /** + * Gets the deadline of the task. + * + * @return string representation of deadline, or empty string for todo tasks. + */ + public String getDeadline() { + if (Objects.equals(this.getTaskType(), "D")) { + return this.getDeadline(); + } else { + return ""; + } + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Task)) { + return false; + } + + Task task = (Task) other; + return Objects.equals(this.task, task.task) + && this.status == task.status + && this.module == task.module + && Objects.equals(this.type, task.type) + && Objects.equals(this.by, task.by); + } + + +} diff --git a/src/main/java/seedu/address/model/group/tasks/TaskInitializer.java b/src/main/java/seedu/address/model/group/tasks/TaskInitializer.java new file mode 100644 index 00000000000..d2ec788d203 --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/TaskInitializer.java @@ -0,0 +1,58 @@ +package seedu.address.model.group.tasks; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.model.group.exceptions.TaskException; + +/** + * A utility class for initializing tasks and creating a TaskList. + */ +public class TaskInitializer { + /** + * Initializes tasks for different modules and creates a TaskList. + * + * @return A TaskList with the initialized tasks. + * @throws TaskException If there is an issue with task initialization. + */ + public static TaskList initializeTasks() throws TaskException { + // Create an empty TaskList + TaskList taskList = new TaskList(); + + List allTasks = new ArrayList<>(); + + // Create tasks for CS2101 + allTasks.add(new Todo("Upload video of OP1.", + TaskStatus.NOT_DONE, TaskModule.CS2101)); + allTasks.add(new Todo("Complete peer review for OP2.", + TaskStatus.NOT_DONE, TaskModule.CS2101)); + allTasks.add(new Deadline("Submit slides for OP2.", + TaskStatus.NOT_DONE, TaskModule.CS2101, "29/10/2023 2359")); + allTasks.add(new Deadline("Complete peer review.", + TaskStatus.NOT_DONE, TaskModule.CS2101, "02/11/2023 2359")); + allTasks.add(new Todo("Research on the SCQA framework.", + TaskStatus.NOT_DONE, TaskModule.CS2101)); + allTasks.add(new Deadline("Plan for OP2.", + TaskStatus.NOT_DONE, TaskModule.CS2101, "24/10/2023 2359")); + allTasks.add(new Deadline("Submit UG.", + TaskStatus.NOT_DONE, TaskModule.CS2101, "11/11/2023 2359")); + + // Create tasks for CS2103T + allTasks.add(new Todo("Complete mid semester review form.", + TaskStatus.NOT_DONE, TaskModule.CS2103T)); + allTasks.add(new Deadline("Add demo screenshots to project notes.", + TaskStatus.NOT_DONE, TaskModule.CS2103T, "20/11/2023 2359")); + allTasks.add(new Deadline("Release v1.3.trial jar file.", + TaskStatus.NOT_DONE, TaskModule.CS2103T, "27/10/2023 2359")); + allTasks.add(new Deadline("Wrap up milestone 1.3.", + TaskStatus.NOT_DONE, TaskModule.CS2103T, "03/11/2023 2359")); + allTasks.add(new Deadline("Finalise TP.", + TaskStatus.NOT_DONE, TaskModule.CS2103T, "17/11/2023 2359")); + allTasks.add(new Todo("Update DG for each feature.", + TaskStatus.NOT_DONE, TaskModule.CS2103T)); + + // Add the tasks to the TaskList + taskList.addTasks(allTasks); + return taskList; + } +} diff --git a/src/main/java/seedu/address/model/group/tasks/TaskList.java b/src/main/java/seedu/address/model/group/tasks/TaskList.java new file mode 100644 index 00000000000..0414be34d1a --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/TaskList.java @@ -0,0 +1,128 @@ +package seedu.address.model.group.tasks; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a list of tasks in a group. + */ +public class TaskList { + private List tasks; + + /** + * Constructs an empty TaskList. + */ + public TaskList() { + tasks = new ArrayList<>(); + } + + /** + * Constructs a TaskList with the given initial tasks. + * + * @param initialTasks The initial tasks to populate the TaskList. + */ + public TaskList(List initialTasks) { + tasks = new ArrayList<>(initialTasks); + } + + /** + * Adds a task to the task list. + * + * @param task The task to add. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes a task from the task list at the specified index. + * + * @param taskIndex The index of the task to delete. + */ + public void deleteTask(int taskIndex) { + if (taskIndex >= 0 && taskIndex < tasks.size()) { + tasks.remove(taskIndex); + } + } + + /** + * Returns the list of tasks in the task list. + * + * @return The list of tasks in the task list. + */ + public List getTasks() { + return tasks; + } + + /** + * Returns the task at index in the task list. + * + * @param index The index of task to be returned. + * @return The task at the index. + */ + public Task getTask(int index) { + return tasks.get(index); + } + + /** + * Checks if a task with the specified task type and task description exists in the task list. + * + * @param taskType The type of the task. + * @param taskDescription The description of the task. + * @return True if a matching task exists, false otherwise. + */ + public boolean isTaskInAllTasks(String taskType, String taskDescription) { + for (Task task : tasks) { + if (task.getTask().equals(taskDescription) && task.getTaskType().equals(taskType)) { + return true; + } + } + return false; + } + + /** + * Adds a list of tasks to the task list. + * + * @param tasks The list of tasks to add. + */ + public void addTasks(List tasks) { + this.tasks.addAll(tasks); + } + + /** + * Returns true if the task list is empty. + * + * @return True if the task list is empty, false otherwise. + */ + public boolean isEmpty() { + return tasks.isEmpty(); + } + + /** + * Returns a copy of the list of tasks in the task list. + * + * @return A copy of the list of tasks. + */ + public List getTaskList() { + return new ArrayList<>(tasks); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String tempStatus; + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.getStatus() == TaskStatus.NOT_DONE) { + tempStatus = "❌"; + } else { + tempStatus = "✅"; + } + result.append(tempStatus).append(" ").append(task.getType()).append(" ").append(i + 1).append(".") + .append(" ").append(task.getModule()).append(" ").append(task.getTask()) + .append(" ").append(task.getBy()).append("\n"); + + } + return String.valueOf(result); + } +} diff --git a/src/main/java/seedu/address/model/group/tasks/TaskModule.java b/src/main/java/seedu/address/model/group/tasks/TaskModule.java new file mode 100644 index 00000000000..d0885e8d9d7 --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/TaskModule.java @@ -0,0 +1,43 @@ +package seedu.address.model.group.tasks; + +/** + * The `TaskModule` enum represents the module for a task, which can be either CS2103T or CS2101. + * It provides symbolic representations for the task module. + */ +public enum TaskModule { + /** + * Represents the CS2103T module. + */ + CS2103T, + + /** + * Represents the CS2101 module. + */ + CS2101; + + public static final String MESSAGE_CONSTRAINTS = "Module must either be CS2103T or CS2101"; + + /** + * Returns the string representation of the task module. + * + * @return A string representation of the task module. + */ + @Override + public String toString() { + return name(); // This returns the name of the enum constant (CS2103T or CS2101). + } + /** + * Checks if the given string is a valid task module. + * + * @param test The string to test. + * @return True if the string is a valid task module, false otherwise. + */ + public static boolean isValidModule(String test) { + for (TaskModule module : TaskModule.values()) { + if (module.toString().equals(test)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/group/tasks/TaskStatus.java b/src/main/java/seedu/address/model/group/tasks/TaskStatus.java new file mode 100644 index 00000000000..d44ca02cb13 --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/TaskStatus.java @@ -0,0 +1,64 @@ +package seedu.address.model.group.tasks; + +/** + * The `TaskStatus` enum represents the status of a task, which can be either done or not done. + * It provides symbolic representations for task status and allows converting status to a string. + */ +public enum TaskStatus { + /** + * Represents a task that is marked as done. + */ + DONE("DONE"), + + /** + * Represents a task that is not marked as done. + */ + NOT_DONE("NOT_DONE"); + + + public static final String MESSAGE_CONSTRAINTS = "Status must either be DONE or NOT_DONE"; + private final String symbol; + + /** + * Constructs a `TaskStatus` enum value with the specified symbolic representation. + * + * @param symbol The symbolic representation of the task status. + */ + TaskStatus(String symbol) { + this.symbol = symbol; + } + + /** + * Returns the symbolic representation of the task status. + * + * @return A string representation of the task status. + */ + + @Override + public String toString() { + return symbol; + } + + /** + * Checks if the given string is a valid task status. + * + * @param test The string to test. + * @return True if the string is a valid task status, false otherwise. + */ + public static boolean isValidStatus(String test) { + for (TaskStatus status : TaskStatus.values()) { + if (status.toString().equals(test)) { + return true; + } + } + return false; + } + + public String getSymbol() { + if (this == DONE) { + return "✅"; + } else { + return "❌"; + } + } +} diff --git a/src/main/java/seedu/address/model/group/tasks/Todo.java b/src/main/java/seedu/address/model/group/tasks/Todo.java new file mode 100644 index 00000000000..c722851b56a --- /dev/null +++ b/src/main/java/seedu/address/model/group/tasks/Todo.java @@ -0,0 +1,21 @@ +package seedu.address.model.group.tasks; + +/** + * The `Todo` class represents a to-do task, which is a basic type of task with a description. + * It inherits from the `Task` class and provides specific implementations for to-do tasks. + */ +public class Todo extends Task { + + /** + * Constructs a new `Todo` task with the given description, status, and module. + * + * @param task The description of the to-do task. + * @param status The status of the task (complete or incomplete). + * @param module The module the task is assigned to (CS2103T or CS2101). + */ + public Todo(String task, TaskStatus status, TaskModule module) { + super(task, status, module, "T", ""); + + } + +} 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 469a2cc9a1e..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,65 +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) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Address)) { - return false; - } - - Address otherAddress = (Address) other; - return value.equals(otherAddress.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Description.java b/src/main/java/seedu/address/model/person/Description.java new file mode 100644 index 00000000000..4e7fd084c93 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Description.java @@ -0,0 +1,53 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's description in StudentConnect. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = "Descriptions should not be left blank or exceed 150 characters."; + + public static final String VALIDATION_REGEX = "^.{1,150}$"; + + public final String value; + + + /** + * Constructs a {@code Description} with the specified description value. + * + * @param description The description value. Must not be null. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(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 Description // instanceof handles nulls + && value.equals(((Description) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..530b9bd6db3 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in StudentConnect. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -14,22 +14,14 @@ 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. It should also not exceed 20 characters\n" + + "2. This is followed by a '@' and then a domain name. The domain name should be 'u.nus.edu' "; // alphanumeric and special characters - private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore + private static final String ALPHANUMERIC_NO_UNDERSCORE = "[A-Za-z0-9" + SPECIAL_CHARACTERS + "]{1,20}"; 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; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; + private static final String DOMAIN_PART_REGEX = "u\\.nus\\.edu"; + public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_PART_REGEX; public final String value; diff --git a/src/main/java/seedu/address/model/person/Gender.java b/src/main/java/seedu/address/model/person/Gender.java new file mode 100644 index 00000000000..2d137bd42c2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Gender.java @@ -0,0 +1,61 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's gender. + * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)} + */ +public class Gender { + + public static final String MESSAGE_CONSTRAINTS = + "Gender should be 'M' or 'F' (case-insensitive)."; + + public static final String VALIDATION_REGEX = "(?i)[MF]"; // Case-insensitive regex for M or F + + public final String value; + + /** + * Constructs a {@code Gender}. + * + * @param gender A valid gender ('M' or 'F'). + */ + public Gender(String gender) { + requireNonNull(gender); + checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS); + value = gender.toUpperCase(); // Store as uppercase to ensure consistency + } + + /** + * Returns true if a given string is a valid gender ('M' or 'F'). + */ + public static boolean isValidGender(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Gender)) { + return false; + } + + Gender otherGender = (Gender) other; + return value.equals(otherGender.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Major.java b/src/main/java/seedu/address/model/person/Major.java new file mode 100644 index 00000000000..bb200d76a44 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Major.java @@ -0,0 +1,143 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's major in StudentConnect. + * Guarantees: immutable; is valid as declared in {@link #isValidMajor(String)} + */ +public class Major { + + public static final String MESSAGE_CONSTRAINTS = "Majors should not be blank and must be a valid" + + " major offered at NUS.\n" + + "Valid major list can be found in the user guide.\n" + + "The user guide can be found by using the 'help' command."; + + private static final String[] VALID_NUS_MAJORS = { + "Accountancy", + "Actuarial Studies", + "Anthropology", + "Architecture", + "Biological Sciences", + "Biomedical Engineering", + "Business Administration", + "Business Administration (Accountancy)", + "Business Analytics", + "Chemical Engineering", + "Chemistry", + "Chinese Language", + "Chinese Studies", + "Communications and New Media", + "Civil Engineering", + "Computer Engineering", + "Computer Science", + "Data Science and Analytics", + "Data Science and Economics", + "Dentistry", + "Economics", + "Electrical Engineering", + "English Language", + "English Literature", + "Environmental Engineering", + "Environmental Studies", + "Food Science and Technology", + "Geography", + "Global Studies", + "History", + "Japanese Studies", + "Industrial Design", + "Industrial Engineering", + "Industrial and Systems Engineering", + "Information Systems", + "Information Security", + "Infrastructure and Project Management", + "Landscape Architecture", + "Law", + "Life Sciences", + "Malay Studies", + "Management", + "Marketing", + "Materials Science and Engineering", + "Mathematics", + "Mechanical Engineering", + "Medicine", + "Pharmacy", + "Pharmaceutical Science", + "Philosophy", + "Philosophy, Politics, and Economics", + "Physics", + "Political Science", + "Psychology", + "Quantitative Finance", + "Real Estate", + "Social Work", + "Sociology", + "South Asian Studies", + "Southeast Asian Studies", + "Statistics", + "Systems Engineering", + "Theatre Studies", + "Urban Studies", + "Visual Communications", + "Others" + }; + + public final String value; + + /** + * Constructs a {@code Major} with the specified major value. + * + * @param major The major value. Must not be null. + */ + public Major(String major) { + requireNonNull(major); + checkArgument(isValidMajor(major), MESSAGE_CONSTRAINTS); + value = capitaliseFirstLetterOfEachWord(major); + } + + private String capitaliseFirstLetterOfEachWord(String text) { + String[] words = text.split("\\s"); + StringBuilder result = new StringBuilder(); + + for (String word : words) { + if (!word.isEmpty()) { + result.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1).toLowerCase()); + result.append(" "); // Add a space between words + } + } + if (result.length() > 0) { + result.setLength(result.length() - 1); + } + return result.toString(); + } + + /** + * Returns if a given string is a valid major offered at NUS. + */ + public static boolean isValidMajor(String major) { + for (String validMajor : VALID_NUS_MAJORS) { + if (major.equalsIgnoreCase(validMajor)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Major // instanceof handles nulls + && value.equals(((Major) 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/person/Name.java index 173f15b9b00..855e05fd77a 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -4,19 +4,20 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in StudentConnect. * 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 should only contain alphabetical characters and spaces, and it should not be blank. " + + "It should also not exceed 30 characters."; - /* + /** * 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 = "^(?=\\s*\\S)[A-Za-z ]{1,30}$"; public final String fullName; @@ -28,7 +29,23 @@ public class Name { public Name(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + fullName = capitaliseFirstLetterOfEachWord(name); + } + + private String capitaliseFirstLetterOfEachWord(String text) { + String[] words = text.split("\\s"); + StringBuilder result = new StringBuilder(); + + for (String word : words) { + if (!word.isEmpty()) { + result.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1).toLowerCase()); + result.append(" "); // Add a space between words + } + } + if (result.length() > 0) { + result.setLength(result.length() - 1); + } + return result.toString(); } /** @@ -63,5 +80,4 @@ public boolean equals(Object other) { public int hashCode() { return fullName.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..a5acfab9cb9 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsIgnoreCase(person.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Nationality.java b/src/main/java/seedu/address/model/person/Nationality.java new file mode 100644 index 00000000000..fa1ed56a867 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Nationality.java @@ -0,0 +1,61 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's nationality in StudentConnect. + * Guarantees: immutable; is valid as declared in {@link #isValidNationality(String)} + */ +public class Nationality { + + public static final String MESSAGE_CONSTRAINTS = + "Nationality should be either 'local' or 'foreigner'"; + + public static final String VALIDATION_REGEX = "(?i)local|foreigner"; + + public final String value; + + /** + * Constructs a {@code Nationality}. + * + * @param nationality A valid nationality. + */ + public Nationality(String nationality) { + requireNonNull(nationality); + checkArgument(isValidNationality(nationality), MESSAGE_CONSTRAINTS); + value = nationality.toLowerCase(); // Store as lowercase to ensure consistency + } + + /** + * Returns true if a given string is a valid nationality. + */ + public static boolean isValidNationality(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Nationality)) { + return false; + } + + Nationality otherNationality = (Nationality) other; + return value.equals(otherNationality.value); + } + + @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 index abe8c46b535..1af61e47cd9 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -8,61 +8,83 @@ import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** - * Represents a Person in the address book. + * Represents a Person/Student in StudentConnect. * 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<>(); + private final Major major; + private final Year year; + private final Description description; + private final Set socialMediaLinks = new HashSet<>(); + private final Set tutorials = new HashSet<>(); + private final Nationality nationality; + private final Gender gender; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Major major, Year year, Email email, Description description, Set tutorials, + Set socialMediaLinks, Nationality nationality, Gender gender) { + requireAllNonNull(name, major, year, email, description, tutorials, socialMediaLinks, nationality, gender); this.name = name; - this.phone = phone; + this.major = major; + this.year = year; this.email = email; - this.address = address; - this.tags.addAll(tags); + this.description = description; + this.tutorials.addAll(tutorials); + this.socialMediaLinks.addAll(socialMediaLinks); + this.nationality = nationality; + this.gender = gender; } public Name getName() { return name; } - public Phone getPhone() { - return phone; + public Major getMajor() { + return major; + } + + public Year getYear() { + return year; } public Email getEmail() { return email; } - public Address getAddress() { - return address; + public Description getDescription() { + return description; } - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public Set getTutorials() { + return Collections.unmodifiableSet(tutorials); + } + + public Set getSocialMediaLinks() { + return Collections.unmodifiableSet(socialMediaLinks); + } + + public Nationality getNationality() { + return nationality; + } + + public Gender getGender() { + return gender; } /** - * Returns true if both persons have the same name. + * Returns true if both persons have the same email. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { @@ -71,7 +93,7 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getEmail().equals(getEmail()); } /** @@ -91,27 +113,34 @@ public boolean equals(Object other) { Person otherPerson = (Person) other; return name.equals(otherPerson.name) - && phone.equals(otherPerson.phone) - && email.equals(otherPerson.email) - && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && major.equals(otherPerson.major) + && year.equals(otherPerson.year) + && email.equals(otherPerson.email) + && description.equals(otherPerson.description) + && tutorials.equals(otherPerson.tutorials) + && socialMediaLinks.equals(otherPerson.socialMediaLinks) + && nationality.equals(otherPerson.nationality) + && gender.equals(otherPerson.gender); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, major, year, email, description, tutorials, socialMediaLinks, nationality, gender); } @Override public String toString() { return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); + .add("name", name) + .add("major", major) + .add("year", year) + .add("email", email) + .add("description", description) + .add("tutorials", tutorials) + .add("socialMediaLinks", socialMediaLinks) + .add("nationality", nationality) + .add("gender", gender) + .toString(); } - } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Year.java similarity index 51% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/person/Year.java index d733f63d739..f87a0ff8e8a 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Year.java @@ -4,32 +4,33 @@ 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)} + * Represents a Student's numeric year level. + * Guarantees: immutable; is valid as declared in {@link #isValidYear(String)} */ -public class Phone { - +public class Year { 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,}"; + "Year should only contain numbers, and it should be 1 digit long between 1 and 6."; + + public static final String VALIDATION_REGEX = "^[1-6]$"; + public final String value; /** - * Constructs a {@code Phone}. + * Constructs a {@code Year}. * - * @param phone A valid phone number. + * @param year A valid year. */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; + public Year(String year) { + requireNonNull(year); + checkArgument(isValidYear(year), MESSAGE_CONSTRAINTS); + value = year; } /** - * Returns true if a given string is a valid phone number. + * Returns true if a given string is a valid year. */ - public static boolean isValidPhone(String test) { + public static boolean isValidYear(String test) { return test.matches(VALIDATION_REGEX); } @@ -45,17 +46,16 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof Phone)) { + if (!(other instanceof Year)) { return false; } - Phone otherPhone = (Phone) other; - return value.equals(otherPhone.value); + Year otherYear = (Year) other; + return value.equals(otherYear.value); } @Override public int hashCode() { return value.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/socialmedialink/SocialMediaLink.java b/src/main/java/seedu/address/model/socialmedialink/SocialMediaLink.java new file mode 100644 index 00000000000..87334b89284 --- /dev/null +++ b/src/main/java/seedu/address/model/socialmedialink/SocialMediaLink.java @@ -0,0 +1,62 @@ +package seedu.address.model.socialmedialink; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the Social Media Links in StudentConnect. + * Guarantees: immutable; name is valid as declared in {@link #isValidSocialMediaLink(String)} + */ +public class SocialMediaLink { + + public static final String MESSAGE_CONSTRAINTS = "Social media links should start with \"http://\", or " + + "\"https://\", followed by one or more alphanumeric characters, dots, or hyphens in the domain name"; + public static final String VALIDATION_REGEX = "^(https?|ftp)://[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}(:[0-9]+)?(/.*)?$"; + + public final String socialMediaLink; + + /** + * Constructs a {@code SocialMedia}. + * + * @param socialMediaLink A valid social media link. + */ + public SocialMediaLink(String socialMediaLink) { + requireNonNull(socialMediaLink); + checkArgument(isValidSocialMediaLink(socialMediaLink), MESSAGE_CONSTRAINTS); + this.socialMediaLink = socialMediaLink; + } + + /** + * Returns true if a given string is a valid social media link. + */ + public static boolean isValidSocialMediaLink(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SocialMediaLink)) { + return false; + } + + SocialMediaLink otherSocialMedia = (SocialMediaLink) other; + return socialMediaLink.equals(otherSocialMedia.socialMediaLink); + } + + @Override + public int hashCode() { + return socialMediaLink.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + socialMediaLink + ']'; + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/tutorial/Tutorial.java b/src/main/java/seedu/address/model/tutorial/Tutorial.java new file mode 100644 index 00000000000..63356870627 --- /dev/null +++ b/src/main/java/seedu/address/model/tutorial/Tutorial.java @@ -0,0 +1,56 @@ +package seedu.address.model.tutorial; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's tutorial in StudentConnect. + * Guarantees: immutable; is valid as declared in {@link #isValidTutorial(String)} + */ +public class Tutorial { + + public static final String MESSAGE_CONSTRAINTS = "Tutorials should be 2-digit numbers between 01 and 22."; + + public static final String VALIDATION_REGEX = "^(0[1-9]|1\\d|2[0-2])$"; + + public final String value; + + /** + * Constructs a {@code Tutorial} with the specified tutorial value. + * + * @param tutorial The tutorial value. Must not be null. + */ + public Tutorial(String tutorial) { + requireNonNull(tutorial); + checkArgument(isValidTutorial(tutorial), MESSAGE_CONSTRAINTS); + value = tutorial; + } + + /** + * Returns true if a given string is a valid tutorial number between 01 and 22. + */ + public static boolean isValidTutorial(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return "T" + value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Tutorial // instanceof handles nulls + && value.equals(((Tutorial) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tutorial/TutorialContainsSlotsPredicate.java b/src/main/java/seedu/address/model/tutorial/TutorialContainsSlotsPredicate.java new file mode 100644 index 00000000000..a70e4f496b2 --- /dev/null +++ b/src/main/java/seedu/address/model/tutorial/TutorialContainsSlotsPredicate.java @@ -0,0 +1,51 @@ +package seedu.address.model.tutorial; + +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Tutorial} matches any of the slots given. + */ +public class TutorialContainsSlotsPredicate implements Predicate { + private final List slots; + + public TutorialContainsSlotsPredicate(List slots) { + this.slots = slots; + } + + @Override + public boolean test(Person person) { + Set tutorialSet = person.getTutorials(); + StringBuilder result = new StringBuilder(); + for (Tutorial tut : tutorialSet) { + result.append(tut.value).append(" "); + } + return slots.stream() + .anyMatch(slot -> StringUtil.containsTutorial(result.toString(), slot)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TutorialContainsSlotsPredicate)) { + return false; + } + + TutorialContainsSlotsPredicate otherTutorialContainsSlotsPredicate = (TutorialContainsSlotsPredicate) other; + return slots.equals(otherTutorialContainsSlotsPredicate.slots); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("slots", slots).toString(); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..b5a994b830c 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,55 +6,89 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; +import seedu.address.model.group.Group; +import seedu.address.model.person.Description; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nationality; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Year; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** * 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")) + new Person(new Name("Alex Yeoh"), new Major("Computer Science"), new Year("2"), + new Email("alexyeoh@u.nus.edu"), new Description("Friendly person"), + getTutorialSet("01", "20"), + getSocialMediaLinkSet("https://example.com/alex"), + new Nationality("local"), new Gender("M")), + new Person(new Name("Bernice Yu"), new Major("Computer Science"), new Year("2"), + new Email("berniceyu@u.nus.edu"), new Description("Colleague from work"), + getTutorialSet("08", "19"), + getSocialMediaLinkSet("https://example.com/bernice"), + new Nationality("local"), new Gender("F")), + new Person(new Name("Charlotte Oliveiro"), new Major("Computer Science"), new Year("2"), + new Email("charlotte@u.nus.edu"), new Description("Neighbour"), + getTutorialSet("05", "06", "10"), + getSocialMediaLinkSet("https://example.com/charlotte"), + new Nationality("local"), new Gender("F")), + new Person(new Name("David Li"), new Major("Computer Science"), new Year("2"), + new Email("lidavid@u.nus.edu"), new Description("Family member"), + getTutorialSet("11"), + getSocialMediaLinkSet("https://example.com/david"), + new Nationality("foreigner"), new Gender("M")), + new Person(new Name("Irfan Ibrahim"), new Major("Computer Science"), new Year("2"), + new Email("irfan@u.nus.edu"), new Description("Classmate"), + getTutorialSet("07", "18"), + getSocialMediaLinkSet("https://example.com/irfan"), + new Nationality("local"), new Gender("M")), + new Person(new Name("Roy Balakrishnan"), new Major("Computer Science"), new Year("2"), + new Email("royb@u.nus.edu"), new Description("Colleague from work"), + getTutorialSet("11", "19", "22"), + getSocialMediaLinkSet("https://example.com/roy"), + new Nationality("foreigner"), new Gender("M")) }; } + public static Group[] getSampleGroups() { + Group sampleGroup1 = new Group(1, new Tutorial("01")); + sampleGroup1.addMember(getSamplePersons()[0]); + + Group sampleGroup2 = new Group(2, new Tutorial("11")); + sampleGroup2.addMember(getSamplePersons()[3]); + sampleGroup2.addMember(getSamplePersons()[5]); + + return new Group[]{ sampleGroup1, sampleGroup2 }; + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Group sampleGroup : getSampleGroups()) { + sampleAb.addGroup(sampleGroup); + } return sampleAb; } - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); + public static Set getTutorialSet(String... tutorials) { + return Arrays.stream(tutorials) + .map(Tutorial::new) + .collect(Collectors.toSet()); } + public static Set getSocialMediaLinkSet(String... strings) { + return Arrays.stream(strings) + .map(SocialMediaLink::new) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java new file mode 100644 index 00000000000..fe284f01f3a --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java @@ -0,0 +1,98 @@ +package seedu.address.storage; + +import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; + +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.group.Group; +import seedu.address.model.group.tasks.TaskList; +import seedu.address.model.person.Person; +import seedu.address.model.tutorial.Tutorial; + +/** + * Jackson-friendly version of {@link Group}. + */ +public class JsonAdaptedGroup { + + public static final String INVALID_NUMBER_MESSAGE = "Group number is invalid!"; + + private final int number; + private final JsonAdaptedTutorial tutorial; + private final List members = new ArrayList<>(); + private final List tasks = new ArrayList<>(); // add a list of JsonAdaptedTask + + /** + * Constructs a {@code JsonAdaptedGroup} with the given group details. + */ + @JsonCreator + public JsonAdaptedGroup(@JsonProperty("number") int number, + @JsonProperty("tutorial") JsonAdaptedTutorial tutorial, + @JsonProperty("members") List members, + @JsonProperty("tasks") List tasks) { // add tasks parameter + + this.number = number; + this.tutorial = tutorial; + if (members != null) { + this.members.addAll(members); + } + if (tasks != null) { + this.tasks.addAll(tasks); + } + } + + /** + * Converts a given {@code Group} into this class for Jackson use. + */ + public JsonAdaptedGroup(Group source) { + number = source.getNumber(); + tutorial = new JsonAdaptedTutorial(source.getTutorial()); + members.addAll(source.getMembers().stream() + .map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); + tasks.addAll(source.getTasks().getTaskList().stream() + .map(JsonAdaptedTask::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted social media object into the model's {@code Group} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted group. + */ + public Group toModelType() throws IllegalValueException { + final List groupMembers = new ArrayList<>(); + final TaskList groupTasks = new TaskList(); + + for (JsonAdaptedPerson member : members) { + groupMembers.add(member.toModelType()); + } + for (JsonAdaptedTask task : tasks) { + groupTasks.addTask(task.toModelType()); + } + + if (number < 1) { + throw new IllegalValueException(INVALID_NUMBER_MESSAGE); + } + + if (tutorial == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Tutorial.class.getSimpleName())); + } + if (!Tutorial.isValidTutorial(tutorial.getTutorial())) { + throw new IllegalValueException(Tutorial.MESSAGE_CONSTRAINTS); + } + final Tutorial modelTutorial = tutorial.toModelType(); + + final Set modelMembers = new HashSet<>(groupMembers); + + return new Group(number, modelTutorial, modelMembers, groupTasks); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..51cd4af3d37 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,12 +10,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Description; import seedu.address.model.person.Email; +import seedu.address.model.person.Gender; +import seedu.address.model.person.Major; import seedu.address.model.person.Name; +import seedu.address.model.person.Nationality; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Year; +import seedu.address.model.socialmedialink.SocialMediaLink; +import seedu.address.model.tutorial.Tutorial; /** * Jackson-friendly version of {@link Person}. @@ -25,25 +30,41 @@ 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 major; + private final String year; private final String email; - private final String address; - private final List tags = new ArrayList<>(); + private final String description; + private final List tutorials = new ArrayList<>(); + private final List socialMediaLinks = new ArrayList<>(); + private final String nationality; + private final String gender; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + public JsonAdaptedPerson(@JsonProperty("name") String name, + @JsonProperty("major") String major, + @JsonProperty("year") String year, + @JsonProperty("email") String email, + @JsonProperty("description") String description, + @JsonProperty("tutorials") List tutorials, + @JsonProperty("socialMediaLinks") List socialMediaLinks, + @JsonProperty("nationality") String nationality, + @JsonProperty("gender") String gender) { this.name = name; - this.phone = phone; + this.major = major; + this.year = year; this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); + this.description = description; + if (tutorials != null) { + this.tutorials.addAll(tutorials); } + if (socialMediaLinks != null) { + this.socialMediaLinks.addAll(socialMediaLinks); + } + this.nationality = nationality; + this.gender = gender; } /** @@ -51,12 +72,18 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone */ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; - phone = source.getPhone().value; + major = source.getMajor().value; + year = source.getYear().value; email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) + description = source.getDescription().value; + tutorials.addAll(source.getTutorials().stream() + .map(JsonAdaptedTutorial::new) + .collect(Collectors.toList())); + socialMediaLinks.addAll(source.getSocialMediaLinks().stream() + .map(JsonAdaptedSocialMedia::new) .collect(Collectors.toList())); + nationality = source.getNationality().value; + gender = source.getGender().value; } /** @@ -65,9 +92,15 @@ public JsonAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); + final List personTutorials = new ArrayList<>(); + final List personSocialMediaLinks = new ArrayList<>(); + + for (JsonAdaptedTutorial tutorial : tutorials) { + personTutorials.add(tutorial.toModelType()); + } + + for (JsonAdaptedSocialMedia socialMediaLink : socialMediaLinks) { + personSocialMediaLinks.add(socialMediaLink.toModelType()); } if (name == null) { @@ -78,13 +111,21 @@ public Person toModelType() throws IllegalValueException { } final Name modelName = new Name(name); - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + if (major == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Major.class.getSimpleName())); + } + if (!Major.isValidMajor(major)) { + throw new ParseException(Major.MESSAGE_CONSTRAINTS); + } + final Major modelMajor = new Major(major); + + if (year == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Year.class.getSimpleName())); } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + if (!Year.isValidYear(year)) { + throw new IllegalValueException(Year.MESSAGE_CONSTRAINTS); } - final Phone modelPhone = new Phone(phone); + final Year modelYear = new Year(year); if (email == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); @@ -94,16 +135,33 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + if (!Description.isValidDescription(description)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + if (nationality == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Nationality.class.getSimpleName())); + } + if (!Nationality.isValidNationality(nationality)) { + throw new IllegalValueException(Nationality.MESSAGE_CONSTRAINTS); } - final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } + if (!Gender.isValidGender(gender)) { + throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS); + } + final Nationality modelNationality = new Nationality(nationality); + final Gender modelGender = new Gender(gender); + final Description modelDescription = new Description(description); + final Set modelTutorials = new HashSet<>(personTutorials); + final Set modelSocialMediaLinks = new HashSet<>(personSocialMediaLinks); + + return new Person(modelName, modelMajor, modelYear, modelEmail, modelDescription, + modelTutorials, modelSocialMediaLinks, modelNationality, modelGender); + + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedSocialMedia.java b/src/main/java/seedu/address/storage/JsonAdaptedSocialMedia.java new file mode 100644 index 00000000000..bdb6714c05a --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedSocialMedia.java @@ -0,0 +1,47 @@ +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.socialmedialink.SocialMediaLink; + +/** + * Jackson-friendly version of {@link SocialMediaLink}. + */ +public class JsonAdaptedSocialMedia { + + private final String socialMediaLink; + + /** + * Constructs a {@code JsonAdaptedSocialMedia} with the given {@code socialMediaLink}. + */ + @JsonCreator + public JsonAdaptedSocialMedia(String socialMediaLink) { + this.socialMediaLink = socialMediaLink; + } + + /** + * Converts a given {@code SocialMedia} into this class for Jackson use. + */ + public JsonAdaptedSocialMedia(SocialMediaLink source) { + socialMediaLink = source.socialMediaLink; + } + + @JsonValue + public String getSocialMediaLink() { + return socialMediaLink; + } + + /** + * Converts this Jackson-friendly adapted social media object into the model's {@code SocialMedia} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted social media. + */ + public SocialMediaLink toModelType() throws IllegalValueException { + if (!SocialMediaLink.isValidSocialMediaLink(socialMediaLink)) { + throw new IllegalValueException(SocialMediaLink.MESSAGE_CONSTRAINTS); + } + return new SocialMediaLink(socialMediaLink); + } +} 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/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..b264edc7f6d --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,84 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.group.tasks.Task; +import seedu.address.model.group.tasks.TaskModule; +import seedu.address.model.group.tasks.TaskStatus; + +/** + * Jackson-friendly version of {@link Task}. + */ +public class JsonAdaptedTask { + private final String task; + private final String status; + private final String module; + private final String type; + private final String by; + + /** + * Constructs a {@code JsonAdaptedTask} with the given task details. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("task") String task, + @JsonProperty("status") String status, + @JsonProperty("module") String module, + @JsonProperty("type") String type, + @JsonProperty("by") String by) { + this.task = task; + this.status = status; + this.module = module; + this.type = type; + this.by = by; + } + + /** + * Converts a given {@code Task} into this class for Jackson use. + */ + public JsonAdaptedTask(Task source) { + task = source.getTask(); + status = source.getStatus().toString(); + module = source.getModule().toString(); + type = source.getType(); + by = source.getBy(); + } + + /** + * Converts this Jackson-friendly adapted task object into the model's {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task. + */ + public Task toModelType() throws IllegalValueException { + if (task == null) { + throw new IllegalValueException("Task's description is missing!"); + } + + if (status == null) { + throw new IllegalValueException("Task's status is missing!"); + } + if (!TaskStatus.isValidStatus(status)) { + throw new IllegalValueException(TaskStatus.MESSAGE_CONSTRAINTS); + } + final TaskStatus modelStatus = TaskStatus.valueOf(status); + + if (module == null) { + throw new IllegalValueException("Task's module is missing!"); + } + if (!TaskModule.isValidModule(module)) { + throw new IllegalValueException(TaskModule.MESSAGE_CONSTRAINTS); + } + final TaskModule modelModule = TaskModule.valueOf(module); + + if (type == null) { + throw new IllegalValueException("Task's type is missing!"); + } + + if (by == null) { + throw new IllegalValueException("Task's deadline is missing!"); + } + + return new Task(task, modelStatus, modelModule, type, by); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTutorial.java b/src/main/java/seedu/address/storage/JsonAdaptedTutorial.java new file mode 100644 index 00000000000..445d7fa4e69 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTutorial.java @@ -0,0 +1,57 @@ +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.tutorial.Tutorial; + +/** + * Jackson-friendly version of {@link Tutorial}. + */ +public class JsonAdaptedTutorial { + + private final String tutorial; + + /** + * Constructs a {@code JsonAdaptedTutorial} with the given {@code tutorial}. + * + * @param tutorial The tutorial value to be used in the adapted object. + */ + @JsonCreator + public JsonAdaptedTutorial(String tutorial) { + this.tutorial = tutorial; + } + + /** + * Converts a given {@code Tutorial} into this class for Jackson use. + * + * @param source The source Tutorial to be converted. + */ + public JsonAdaptedTutorial(Tutorial source) { + tutorial = source.value; + } + + /** + * Gets the tutorial value. + * + * @return The tutorial value as a string. + */ + @JsonValue + public String getTutorial() { + return tutorial; + } + + /** + * Converts this Jackson-friendly adapted tutorial object into the model's {@code Tutorial} object. + * + * @return A Tutorial object. + * @throws IllegalValueException if there were any data constraints violated in the adapted tutorial. + */ + public Tutorial toModelType() throws IllegalValueException { + if (!Tutorial.isValidTutorial(tutorial)) { + throw new IllegalValueException(Tutorial.MESSAGE_CONSTRAINTS); + } + return new Tutorial(tutorial); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index 41e06f264e1..057bf0a1d6e 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -73,6 +73,7 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro requireNonNull(addressBook); requireNonNull(filePath); + addressBook.sortGroups(); FileUtil.createIfMissing(filePath); JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..d176f6af5c7 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,6 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -20,15 +21,19 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_GROUP = "Persons list contains duplicate group(s)."; private final List persons = new ArrayList<>(); + private final List groups = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons and groups. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("groups") List groups) { this.persons.addAll(persons); + this.groups.addAll(groups); } /** @@ -38,6 +43,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { + + private static final String FXML = "ClearConfirmationPopup.fxml"; + + @FXML + private Button yesButton; + + @FXML + private Button cancelButton; + + @FXML + private Label confirmationMessage; + + private boolean isConfirmed = false; + + private Consumer confirmationCallback; + + + + /** + * Creates a new ConfirmationPopup with a new internal Stage. + */ + public ConfirmationPopup() { + super(FXML, new Stage()); + // Make the stage modal + getRoot().initModality(Modality.APPLICATION_MODAL); + initialize(); + } + + /** + * Sets the callback to be executed when the confirmation is received. + * + * @param callback The callback to be executed. + */ + public void setConfirmationCallback(Consumer callback) { + this.confirmationCallback = callback; + } + + /** + * Shows the confirmation popup with the specified message. + * + */ + public void show() { + confirmationMessage.setText("Are you sure you would like to delete all the data? \n" + + "You will not be able to undo this action later."); + getLogger().fine("Showing confirmation popup."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Initializes the confirmation popup, setting up event handlers for the "Yes" and "Cancel" buttons. + */ + @FXML + private void initialize() { + yesButton.setOnAction(event -> { + isConfirmed = true; + getRoot().close(); + if (confirmationCallback != null) { + confirmationCallback.accept(true); + } + }); + + cancelButton.setOnAction(event -> getRoot().close()); + } + + /** + * Gets the logger for the ConfirmationPopup class. + * + * @return The logger for the ConfirmationPopup class. + */ + private static Logger getLogger() { + return LogsCenter.getLogger(ConfirmationPopup.class); + } + + /** + * Checks if the confirmation popup is currently being shown. + * + * @return True if the confirmation popup is showing, false otherwise. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Focuses on the confirmation popup. + */ + public void focus() { + getRoot().requestFocus(); + } + + /** + * Checks if the confirmation was confirmed. + * + * @return True if the confirmation was confirmed, false otherwise. + */ + public boolean isConfirmed() { + return isConfirmed; + } + + /** + * Gets the "Yes" button of the confirmation popup. + * + * @return The "Yes" button. + */ + public Button getYesButton() { + return yesButton; + } + + /** + * Gets the "Cancel" button of the confirmation popup. + * + * @return The "Cancel" button. + */ + public Button getCancelButton() { + return cancelButton; + } + +} diff --git a/src/main/java/seedu/address/ui/GroupCard.java b/src/main/java/seedu/address/ui/GroupCard.java new file mode 100644 index 00000000000..02668b0764f --- /dev/null +++ b/src/main/java/seedu/address/ui/GroupCard.java @@ -0,0 +1,109 @@ +package seedu.address.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.group.Group; +import seedu.address.model.person.Person; + +/** + * An UI component that displays information of a {@code Group}. + */ +public class GroupCard extends UiPart { + + private static final String FXML = "GroupListCard.fxml"; + private static final Logger logger = Logger.getLogger(GroupCard.class.getName()); + public final Group group; + + @FXML + private HBox cardPane; + @FXML + private Label id; // No need to change the id label style + @FXML + private Label number; + @FXML + private HBox tutorialBox; + @FXML + private Label tutorial; + @FXML + private Label member1; + @FXML + private Label member2; + @FXML + private Label member3; + @FXML + private Label member4; + @FXML + private Label member5; + @FXML + private List -