diff --git a/README.md b/README.md index 13f5c77403f..1f291097982 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -[![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/AY2122S1-CS2103T-F11-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-F11-1/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. +# TimesTable + +This is a contact application aimed at helping freelance tutors to +1. keep track of their tutee information such as contact information, address, etc. +2. keep track of teaching schedule + +# More about this project + +* This is a software engineering project by team F11-1 for NUS module CS2103T of AY2021-22 Sem 1. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). +* The starting codebase 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. + diff --git a/build.gradle b/build.gradle index be2d2905dde..70fe255688e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'jacoco' } -mainClassName = 'seedu.address.Main' +mainClassName = 'seedu.times.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'timestable.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..7460e0c433b 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,77 @@ 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` +# Project team -## Project team +### Bernard Wan De Yuan -### John Doe + - +[[github](https://github.com/bernardwan)] -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[portfolio](team/bernardwan.md)] -* Role: Project Advisor +Personal email: bernardwan99@gmail.com
+School email: bernardwan@u.nus.edu -### Jane Doe +Year 2 CS Student +* Role: Deliverables and Deadlines - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +### Lin Zhiwei -* Role: Team Lead -* Responsibilities: UI + + +[[github](https://github.com/softmagnet)] + +[[portfolio](team/softmagnet.md)] + +Personal email: zhiwei3141@gmail.com
+School email: zhiwei_lin@u.nus.edu + +Year 2 CS Student +* Role: Code quality and testing + +### Ong Zheng Lin -### Johnny Doe + - +[[github](https://github.com/Ongzl)] -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[portfolio](team/ongzl.md)] -* Role: Developer -* Responsibilities: Data +Personal email: ongzhenglin1999@gmail.com
+School email: ong.zhenglin@u.nus.edu -### Jean Doe +Year 2 CS Student +* Role: Documentation - +### Kevin Chua Kian Chun -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Developer -* Responsibilities: Dev Ops + Threading +[[github](https://github.com/yourally2)] -### James Doe +[[portfolio](team/yourally2.md)] - +Personal email: kevinchua6@gmail.com
+School email: kevinchua@u.nus.edu -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +Year 2 CS Student +* Role: Integration +* Subrole: Intellij and Git expert -* Role: Developer -* Responsibilities: UI +### Stuart Long Chay Boon + + + +[[github](https://github.com/s7u4rt99)] + +[[portfolio](team/s7u4rt99.md)] + +Personal email: stuartlongchayboon@gmail.com
+School email: stuartlong@u.nus.edu + +Year 2 CS Student +* Role: Team Lead +* Subrole: Scheduling and Tracking diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..7daba2586d9 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,100 +2,152 @@ layout: page title: Developer Guide --- -* Table of Contents + +* Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} --------------------------------------------------------------------------------------------------------------------- +- The [`Timetable`](#timetable-ui) feature was inspired by a similar feature in the past project of Pet Store Helper [here](https://github.com/AY1920S2-CS2103-W15-4/main). The implementation of the components of the [`Timetable`](#timetable-ui) feature (`TimetableTuitionClassSlot`, `TimetableDay`, `TimetableRegion` and `TimetableEmptySlot`) has been adapted from them with maximum changes to fit our app. + The implementation of how we [built](#timetable-tab-feature) and designed ([TimetablePanel.java](https://github.com/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/ui/timetabletab/TimetablePanel.java)) the entire Timetable Tab (layout, classes etc) is entirely new. + +--- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2122S1-CS2103T-F11-1/tp/tree/master/docs/diagrams) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +
+
+ ### Architecture -The ***Architecture Diagram*** given above explains the high-level design of the App. +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`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup methods where necessary. +**`Main`** has two classes called [`Main`](https://github.com/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/Main.java) and [`MainApp`](https://github.com/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/MainApp.java). It is responsible for, + +- At app launch: Initializes the components in the correct sequence, and connects them up with each other. +- At shut down: Shuts down the components and invokes cleanup methods where necessary. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. The rest of the App consists of four components. -* [**`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. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +- [**`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. +- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +
**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 1`. 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. +- 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. + +
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/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `StatusBarFooter` etc, and a TabPane consisting of `StudentsUi`, `ClassesUi` and `TimetableUi`. 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/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2122S1-CS2103T-F11-1/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`. +- 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. +- observes the `Command` abstract class in the `Logic` component, because it needs to update when certain commands are run. +- depends on some classes in the `Model` component, as it displays `Student` and `TuitionClass` objects residing in the `Model`. + +#### Students UI + +![StudentsUi Class Diagram](images/StudentsDiagram.png) + +The `StudentListPanel` consists of `StudentCard`s, which displays information about the `Students`. The `StudentListPanel` takes in an `ObservableList`, which builds a `StudentCard` for each student. + +
+ +#### Timetable UI + +Adapted from [here](https://github.com/AY1920S2-CS2103-W15-4/main/tree/master/src/main/java/clzzz/helper/ui/calendar). You can find more details in the [acknowledgements](#acknowledgements) section. + +![TimetableUi Class Diagram](images/TimetableDiagram.png) + +The `TimetablePanel` is made up of `TimetableDay`, `TimetableHeader`, `TimetableTuitionClassSlot` and `TimetableEmptySlot`. +They represent the day panel on the left, the header at the top with the label and timings, the slots representing the `TuitionClass`es and the empty slots between `TuitionClass`es respectively. +The `TimetablePanel` takes in an `ObservableList` to build the Timetable. + +
+ +#### Classes UI + +![Structure of ClassPanel](images/ClassPanelDiagram.png) + +The ClassPanel consists of a `TuitionClassPanel` and a `StudentClassPanel`. +They represent the left and right panels of the GUI respectively. +`TuitionClassPanel` takes in both an `ObservableList` and an `ObservableList`, while +`StudentClassPanel` takes in only an `ObservableList`. +`TuitionClassPanel` requires an `ObservableList` for the purpose of filtering the Student List based on the selected `TuitionClass`. +![img.png](images/ClassPanelImage.png) +`TuitionClassPanel` and `StudentClassPanel` both contain their respective `Card`s for each element in their respective `ObservableList`. +Note that `StudentClassTabCard` is different from the `StudentCard` in the `studenttab` package so we have less coupling and for future extensibility. + +
### 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/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: - +![Logic class diagram](images/LogicClassDiagram.png) + +[comment]: <> () How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). + +1. When `Logic` is called upon to execute a command, it uses the `TimesTableParser` class to parse the user command. +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. The `Command` object may call the `CommandObserver` to update the `UI` if necessary. +1. The command can communicate with the `Model` when it is executed (e.g. to add a `Student`). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -105,151 +157,463 @@ The Sequence Diagram below illustrates the interactions within the `Logic` compo
: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.
+
+ 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 `TimesTableParser` 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 `TimesTableParser` 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/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/model/Model.java) +![Structure of the UI Component](images/ModelClassDiagram.png) 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 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) +- stores the TimesTable data. + - All `Student` objects which are contained in `UniqueStudentList`. + - All `TuitionClass` objects which are contained in `UniqueClassList`. +- stores the currently 'selected' `Student` objects (e.g., results of a search query) as a separate _filtered_ + list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' + e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +- Similarly, the currently 'selected' `TuitionClass` objects are stored in a separated _filtered_ list which is exposed + to outsiders as an unmodifiable `ObservableList`. +- 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). + +The `Student` and `NOK` class (next-of-kin) extends the `Person` class. The `Student` class's structure is as follows: + +![Structure of the UI Component](images/StudentModelClassDiagram.png) + +The structure of the `TuitionClass` class is as follows: -
: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.
+![Structure of the UI Component](images/TuitionClassModelClassDiagram.png) - +
:information_source: + +**Note about the model design:**
+ +The `StudentNameList` contains a `List`, and the diagram omits the relationship that `Student` and `NOK` are subclasses of the abstract class `Person`.
+
### 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/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/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. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) + +- can save both TimesTable data and user preference data in json format, and read them back into corresponding objects. +- inherits from both `TimesTableStorage` and `UserPrefStorage`, which means it can be treated as either one (if only + the functionality of only one is needed). +- depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`). +- `JsonAdaptedStudent` are saved in a `List` and `JsonAdaptedTuitionClass` are saved in a + `List`. +- `JsonAdaptedTag`s are stored in `JsonAdaptedStudent` as a `List`. ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.times.commons` package. + +--- --------------------------------------------------------------------------------------------------------------------- +
## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Classes Tab feature -#### Proposed Implementation +The Classes Tab feature allows one to see the user's classes and each class' corresponding students. -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: +#### 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. +![Structure of Class Ui](images/ClassPanelDiagram.png) -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The class diagram for the Classes Tab feature as shown in the [Classes Ui component](#classes-ui) is replicated here for convenience. +`TuitionClassPanel` and `StudentClassPanel` are both contained in their respective `StackPane` located below their respective `Label`s. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +![Classes Ui Sequence Diagram.png](images/ClassesUiSequenceDiagram.png) -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. +1. `MainWindow#fillInnerParts()` creates a new `ClassPanel` using the `ObservableList` and the `ObservableList` from `Logic`. +2. A `StudentClassPanel` and a `TuitionClassPanel` is created using the `ObservableList` and `ObservableList`. +3. `StudentClassPanel` and `TuitionClassPanel` create their respective cells for each `Student`/`TuitionClass` present. +4. `TuitionClassPanel#setStudentClassList()` is run by taking in the `ListView` from the `StudentClassPanel`. This is to render the students in the `StudentClassPanel` in the `TuitionClassPanel` as well. +5. Afterwards, when a `TuitionClassCard` is double clicked, `TuitionClassCard#onMouseClick()` bound to the fxml file is called, calling `TuitionClassCard#selectTuitionClass()`. +6. The `filtered` method is then run on the `studentList` to return a `newStudentList` which is filtered by all the students belonging to the `tuitionClass`. +7. The `tuitionClassListView` is set to the `newStudentList` created and thus rendered. -![UndoRedoState0](images/UndoRedoState0.png) +
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +### Timetable Tab feature -![UndoRedoState1](images/UndoRedoState1.png) +The Timetable Tab feature is a feature which displays the user's classes in a visual timetable format. -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`. +#### Implementation -![UndoRedoState2](images/UndoRedoState2.png) +The class diagram for Timetable as shown in the [TimetableUI component](#timetable-ui) is replicated here for convenience. +![Timetable Class Diagram](images/TimetableDiagram.png) -
: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`. +
-
+The image below shows the respective parts of the `TimetablePanel`: -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. +![Timetable annotation](images/TimetableAnnotation.png) -![UndoRedoState3](images/UndoRedoState3.png) +- The green box represents the `TimetableDay`, and there are 7 `TimetableDay` parts to represent the 7 days of the week. +- The yellow box represents the `TimetableHeader`, with the box all the way at the left with the label "Time Slots" representing the `TimetableHeaderLabel`, and the others representing the `TimetableHeaderTiming`. There is always 1 `TimetableHeaderLabel` but can have many `TimetableHeaderTiming` parts depending on the earliest start time and latest end time of the week. +- The dark blue box represents the `TimetableEmptySlot`. +- The light blue box represents the `TimetableTuitionClassSlot`. -
: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. +[comment]: <> (Due to the limited size of the application's window, the Timetable UI would adjust itself and starts the days of the Timetable UI with the) -
+[comment]: <> (earliest start timing and ends with the latest end timing so that the timetable is not cluttered. There is a time panel at the top to indicate what) +
-The following sequence diagram shows how the undo operation works: +The sequence diagrams below illustrate how the Timetable UI is built. -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +![Timetable Overall Sequence Diagram](images/TimetableUiSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +1.`MainWindow#fillInnerParts()` creates a new `TimetablePanel` using the `ObservableList` from `Logic`. -
+2.`TimetablePanel#build()` is called in the constructor of `TimetablePanel` to build the Timetable Ui. + +3.`TimetablePanel#build()` starts building the Timetable Ui by first calling `TimetablePanel#buildHeader()` which takes in the `ObservableList`. + +![Timetable Header Sequence Diagram](images/TimetableUiHeaderSequenceDiagram.png) + +4.Based on the `Timetable#buildHeader()` reference frame above, it builds the `TimetableHeaderLabel` first, followed by the `TimetableHeaderTiming`s, starting from the earliest start time of the `ObservableList` until the latest end time of the `ObservableList` in 30 minutes interval. + +![Timetable Day Sequence Diagram](images/TimetableUiDaySequenceDiagram.png) + +5.After `TimetablePanel#buildHeader()` is called, it would call `TimetablePanel#buildDays()`, which builds 7 `TimetableDay` objects to represent the 7 days of the week. -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. +![Timetable Slot Sequence Diagram](images/TimetableUiSlotSequenceDiagram.png) -
: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. +6.Finally, the `TimetablePanel#buildClasses()` is called, which takes in the same `ObservableList` as step 3. It iterates through the _sorted_ `ObservableList`, building a `TimetableTuitionClassSlot` for each of the `TuitionClass`, and placing `TimetableEmptySlot`s in between the `TimetableTuitionClassSlot`s. +7.A listener is attached to the `ObservableList` which updates the Timetable UI whenever there are changes to the `ObservableList`, + such as when a new `TuitionClass` is added, or an existing `TuitionClass` is edited in the `ObservableList`. + +
:information_source: **Note:** This is just a high level explanation of how the Timetable UI is built, with the low level details being abstracted away.
-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. +[comment]: <> (![Timetable Overall Activity Diagram](images/BuildTimetableOverallDiagram.png)) + +[comment]: <> (![TimetableHeader Activity Diagram](images/BuildTimetableHeaderActivityDiagram-Activity\_\_Build_TimetableHeader.png)) + +[comment]: <> (![TimetableDay Activity Diagram](images/BuildTimetableDayActivityDiagram-Activity\_\_Build_TimetableDay.png)) + +[comment]: <> (![TimetableTuitionClassSlot Activity Diagram](images/BuildTimetableTuitionClassSlotsActivityDiagram-Activity\_\_Build_TimetableTuitionClassSlots.png)) + +[comment]: <> (![Find earliest start hour and latest end hour Activity Diagram](images/FindEarliestAndLatestHourActivityDiagram-Activity\_\_Find_earliest_start_hour_and_latest_end_hour.png)) + +
+ +### Observer Pattern + +The Observer Pattern is facilitated by the `CommandObserver` who represents the `Observer`. It observes the abstract `Command` class. + +#### Implementation + +The class diagram below shows how we implemented the structure of the Observer Pattern. + +![Command Observer Pattern](images/CommandObserverPattern.png) + +The `MainWindow` from `UI` component implements both the `Ui` and `CommandObserver` interface as it plays the role of the `Ui` as well as a `CommandObserver`. The `MainWindow` observes the `Command` class, who calls `CommandObserver#updateView()`, `CommandObserver#updateClass()` and `CommandObserver#hideTuitionClassList()` depending on what Command `XYZCommand` is. + +For eg, the `ViewComand` calls `CommandObserver#updateView()` to set the displayed tab of the `CommandObserver` to the tab specified. Additionally, the `AddToClassCommand` calls both `CommandObserver#updateClass()` and `CommandObserver#updateView()` to update the class details and updates the view to the `Classes` tab when we execute the `AddToClassCommand`. + +#### Design Considerations + +1. (Bad) After each command call, set a variable in the `CommandResult` to be the value of the Tab to change to, or a boolean value to see if we need to update the class. The `MainWindow` would then check the variables in `CommandResult` in `MainWindow#executeCommand()`, and updates the tab or class accordingly. + - This is a very bad and inefficient method as the `MainWindow` would have to constantly check the `CommandResult` after every `Command`. + - With the addition of new variables in `CommandResult`, it has the potential to introduce more bugs and make the code more unreadable. Additionally, other classes may also be able to access these variables accidentally, as they have to be `public` in order for `MainWindow` to access them, leading to unwanted consequences. +2. (Good) Follow the Observer Pattern taught in CS2103T, where we register the `MainWindow` as a `CommandObserver` to observe the abstract `Command` class, and will only get updated when necessary. + - This is a more efficient method as the `MainWindow` does not have to always check the `CommandResult` after each execution. + - No new variables are needed to be introduced into the `CommandResult`, keeping our code neater and less bug prone. + +
+ +### `add` and `edit` commands to include next-of-kin `nok` + + +#### Implementation +The solution that we came up with was very simple. we just had to split the string by `/nok` and put both of the split +portions into the parser. Modifying the existing `parser` was not needed at all. we learnt that oftentimes for a small feature, we don't have +to go for the most extensible or "smart" solution, but the simplest to understand. + +#### Design Considerations +Initially, it appeared challenging to us because the current `Parser` is only able to parse contents _between_ tags, but not encompass other tags within recursively. +For example, `add n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` requires us to parse the tags before and after `nok/` _separately_, which is a challenging problem to think about at first. + +1. (Bad) We thought of a recursive implementation of the `Parser`, calling `parse` methods again on the two halves whenever we find an `/nok` tag, but +it proved to be too lofty and unneccesarily complicated. Moreover, using recursion in applications is not recommended due to the high potential +of unseen bugs. + +2. (Bad) We thought of modifying the parser such that it always checks for the `/nok` tag (or any of the tags specified in the parameter) _first_, before parsing the other tags +in the normal way. However, this was again too large scale for a single command with the same parameter. + + +
+ +### Find commands + +The find commands are a common group of commands that allows users to quickly find `Student` or `TuitionClass`. They are: +* `findname` +* `findtag` +* `findclass` +* `findclassname` + +#### Implementation of the command -![UndoRedoState4](images/UndoRedoState4.png) +The implementation of all search-related commands such as `findtag`, `findname`, `findclass` and `findclassname` uses a +common approach of setting a `Predicate` inside the corresponding `FilteredList` class. As mentioned above in [`Model`](#model-component) +section, the filtered list either contains an `ObservableList` or `ObservableList` that is bound to the +UI such that UI is responsive to any changes in the list and these changes can be brought forward by setting a new +`Predicate`. One should also note that the default `Predicate` always returns a boolean `true` which means no `Student` +or `TuitionClass`is filtered out at the start. -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. +The sequence diagram when a `findtag` command is executed is as follows: -![UndoRedoState5](images/UndoRedoState5.png) +![Sequence of execution when a findtag command is executed](images/FindSequenceDiagram.png) -The following activity diagram summarizes what happens when a user executes a new command: +The rest of the find command works the same way but note that for `findclass` and `findclassname`, they are calling the +`setPredicate` method of `filteredTuitionClass` instead. - +#### Implementation of find predicates -#### Design considerations: +Furthermore, to fully understand the find command, we also have to understand how `Predicate` works. The `Predicate`s used +that filters out students or classes are typical java `Predicate`. For each searchable attribute, a new class must be +created that implements `Predicate` with the right generic type (i.e. predicate class for filtering `Student` must +implement `Predicate`, and predicate class for filtering `TuitionClass` must implement `Predicate`). +Each custom predicate class contains a `List` of search strings that would be used to match against the tested items +in the search. -**Aspect: How undo & redo executes:** +
-* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +### View feature -* **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. +The `Students` tab, `Classes` tab and `Timetable` tab, are parts of the [`UI Component`](#ui-component). +Navigation between these tabs without the mouse is crucial for our application as the target audience are people who prefer keyboard to mouse or voice commands. -_{more aspects and alternatives to be added}_ +The `view` feature is facilitated by the `ViewCommand`, which extends the abstract `Command` class. The `ViewCommand` sets the displayed tab to be the tab specified by the user. -### \[Proposed\] Data archiving +#### Implementation -_{Explain here how the data archiving feature will be implemented}_ +The sequence diagram for the `view` command is shown below. +![Sequence Diagram for view command](images/ViewSequenceDiagram.png) --------------------------------------------------------------------------------------------------------------------- +The `ViewCommand` calls the `CommandObserver#updateView(TabName)`, which updates the view of the `CommandObserver`s watching the `Command` abstract class to the `TabName` specified. +In this case, the only `CommandObserver` is the `MainWindow`, thus it updates the view of the `MainWindow` to display the `TIMETABLE` Tab at index 2. + +`TabName` is an enumeration which represents the three tabs (`Students`, `Classes`, and `Timetable`), and their respective tab index (0 for `Students`, 1 for `Classes` and 2 for `Timetable`). + +
+ +### Sort feature + +The `sort` feature allows sorting of the `Student`s and `TuitionClass`es. It is able to sort it by `Student` name or `ClassTiming`, in ascending or descending order. + +The `sort` feature is facilitated by the `SortCommand`, which extends the abstract `Command` class. + +#### Implementation + +The sequence diagram for the `sort` command is shown below. + +![Sequence Diagram for sort command](images/SortSequenceDiagram.png) + +The `sort` command sorts the `ObservableList` or the `ObservableList` in the `Model` component, whose results gets immediately reflected in their respective `Students` tab or `Classes` Tab. +After sorting, the `Command` sets the view to switch to their respective tabs, so that the user would be able to see the changes. + +
+ +### Adding a Student to a class + +`addtoclass` command adds an existing `Student` into an existing `TuitionClass`. + +#### Implementation of the parser + +`addtoclass` command's parser `AddToClassCommandParser` works by parsing indexes in the user input and +generating a `List` whereby the first index will be for the `TuitionClass` receiving the `Student`s and the rest +of the index being the `Student`s to be added into the `TuitionClass`. The only thing to note is that the parser +will only see zero and negative indices as invalid and not +out-of-range indices. This is because at the time of parsing, the model is not accessed to check if the indices are +out-of-range. The reason for this design is to reduce dependency and keep to the single responsibility principle. The +job of the parser should be separated from checking in with the model. + +#### Implementation of the command + +The `addtoclass` command follows an index based format and the `TuitionClass` contains the `Index` of the class to add +the new students to and a `List` of `Index` of students to be added. The command's execution is composed of various +smaller steps. The steps are listed below: + +1. Check indices are not out-of-range +2. Generate a list of `Name`s to be added to the class +3. Produce the new student `StudentNameList` based on the class's existing `StudentNameList` and the list of `Name`s to + be added +4. Creating the right `EditClassDescriptor` +5. Updating the `Model` with updated `TuitionClass` + +(Note that updating view action is omitted) + +
+ +The following sequence diagram gives an overview of the execution: + +![AddToClass Sequence](images/AddToClassSequenceDiagram.png) + +The sequence diagram for the first reference frame from above: + + ![AddToClass Sequence](images/AddToClassRef1.png) + +The sequence diagram for the second reference frame from above: + +![AddToClass Sequence](images/AddToClassRef2.png) + +
+ +### Removing Student(s) from a Tuition Class +Allows the user to remove one or more `Student`s from a selected `TuitionClass`. + +#### Implementation +The `removefromclass` command follows a similar execution path as other commands. The user input is passed to +the `LogicManager`, which parses the input using the `TimesTableParser` and `RemoveFromClassCommandParser`. +A `RemoveFromClassCommand` is then created with the class and student indices involved in the command. +The command is then executed, interacting with the `Model`. + +An overview of how the `RemoveFromClassCommand` is created is shown by this sequence diagram: + +![RemoveFromClass Overview Sequence Diagram](images/RemoveFromClassOverviewDiagram.png) + +
+ +#### Challenges faced + +The challenging aspect in removing students from a tuition class is in figuring out which students to remove. + +The `RemoveFromClassCommandParser` parses the user input to obtain a list of indexes to be passed to the `RemoveFromClassCommand`. +As such, the `RemoveFromClassCommand` only has access to a list of indices. We can easily obtain the `TuitionClass` object by +simply using the class index with `Model#getFilteredTuitionClassList()`. + +On the other hand, the `TuitionClass` object only stores +the `Name` of each `Student` in the `TuitionClass`. This was done as we use immutable objects throughout TimesTable. As such, if the user +modifies any of the fields of a `Student` using the `edit` command, then we would have to reflect the change throughout all the tuition classes +of the student. By only storing the `Name` of the `Student` in the `TuitionClass` object, then we only have to update all the `TuitionClass` +objects if the user modifies the `Name` field of the `Student`. + +However, this decision results in added complexity when removing students from tuition classes. This is due to the fact that the `Name`s +stored in the `TuitionClass` object are in the order in which the students were added, but the order of the students as displayed in the +GUI is dependent on the `sort` and `find` commands used by the user. As such, the indices entered by the user do not correspond with the +actual `Names` stored in the `TuitionClass` object. + +To solve this problem, we have to obtain the list of students in the order displayed by the GUI. To obtain this list, +we use the same method that the GUI uses to display the list in the first place. We use `Model#getFilteredStudentList()`, +then filter it to the students whose `Name`s are in the `TuitionClass` that we are concerned with. From here, we can now use the +student indices entered by the user to obtain the `Name`s of the corresponding `Students` in the filtered list. Then we can create +a new `TuitionClass` object with an updated list of `Name`s and replace the old `TuitionClass` with this new one. + +An overview of the process is shown below: + +![RemoveFromClass Internal Diagram](images/RemoveFromClassCommandSequenceDiagram.png) + +
+ +### Deleting Tuition Class + +Deletes a `TuitionClass` from the `classes` list in the `Classes` tab, to delete a tuition class, the `deleteclass` +command is used. + +#### Implementation + +1. The `DeleteCommandParser` parses the user input to obtain the index of the class to be deleted. +2. A `DeleteCommand` is created with the index of the tuition class to be deleted. +3. The `DeleteCommand#execute()` is run, the TimesTable is searched to find the tuition class to be deleted. +4. That tuition class is then deleted from the `Model` and the `UniqueClassList` by extension. + +A diagram of the procedure is shown below: + +![DeleteClass Sequence Diagram](images/DeleteClassSequenceDiagram.png) + +![DeleteClass Ref Sequence Diagram](images/DeleteClassRef1.png) + +### Adding Tuition Class + +Adds a new tuition class into the `classes` list in the `Classes` tab with all the relevant detail, to add a tuition +class, the `addclass` command is used. + +#### Implementation + +1. The `AddClassCommandParser` parses the user input to obtain 4 parameters: `ClassName`, `ClassTiming`, `Rate` and + `Location`. +2. The parser checks if the user has inputted valid value for these 4 parameter. +3. These 4 parameters and a new empty`StudentNameList` are then used to create a new `TuitionClass` to be passed into a new `AddClassCommand(TuitionClass)` + as an argument. +4. This command is then executed and the new `TuitionClass` is added into the `Model` and into the + `UniqueClassList`, where further checks are done to ensure that there is no overlapping timing between the new + `TuitionClass` that is to be added and other already existing `TuitionClass`es in the list, as TimesTable is made for a + single user and thus designed to not allow overlapping `TuitionClass`es + +The sequence diagram when a `AddClass` command is executed by the LogicManager is as follows: + +![Sequence diagram when AddClass command is executed in LogicManger](images/AddClassSequenceDiagram.png) + +![Reference frame of AddClass command](images/AddClassRef1.png) + +The sequence diagram when a new `TuitionClass` is added to the `Model` is as follows + +![Sequnce diagram in model when class is added](images/AddClassModelSequenceDiagram.png) + +
+ +#### Challenges faced + +The challenging aspect when adding a tuition class to the `UniqueClassList` was making sure that no overlapping +classes were added. + +Initially, the `equals()` method of `TuitionClass` was used to compare `TuitionClass`es, and `ClassTiming` was the +only comparator in the `equals()` method, but this proved problematic later on due to comparing `ClassTiming` +using only the `equals()` method and comparing `TuitionClass`es using only `ClassTiming`. We realized that classes +would still be added even though they overlap as they were not strictly equals and this method of comparison was +very naive. + +After some trial and error with quite a lot of developer's testing, we created a `isOverlapping()` in `TuitionClass` +that checks for overlaps which checks for any overlap where either class will be within the timeframe of one another, +or where the start or end time of one class was within the timeframe of another. This made sure that we had a method +to check for any overlap between classes when adding new class to the `UniqueClassList`, ensuring that no new +classes would be added to the `UniqueClassList` when adding a tuition class to TimesTable. This check also served to +make sure that there would be no overlapping classes resulting from an `editclass` command. + +--- ## **Documentation, logging, testing, configuration, dev-ops** -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) --------------------------------------------------------------------------------------------------------------------- +--- + +
## **Appendix: Requirements** @@ -257,73 +621,371 @@ _{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 - -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app - +- Name: Kevin Bernard Long Zheng Wei +- Age: 28 +- Gender: Male +- Marital status: Single +- Country: Singapore +- Habits: Sleeping, Netflix, Gym, Gaming +- Lifestyle: Code, eat, sleep, gives freelance tuition. + - prefer desktop apps over other types + - can type fast + - prefers typing to mouse interactions + - is reasonably comfortable using CLI apps +- Interest: Loves coding and building apps during his free time with friends. Loves teaching people. +- Values: Work hard play hard. +- Salary: $12000/mth +- Job: Full time freelance A-levels Mathematics tuition teacher (Size of class: group and one-to-one) + - Has a need to manage a significant number of contacts +- Car: Owns 2 Teslas. +- Home Environment: Lives with parents, older brother and dog. +- Education: NIE graduate. +- Household Description: Sentosa Cove landed property with rich parents. + +**Value proposition**:
+A busy tutor who has a large number of students can find it extremely difficult to + +- manage and organize student information +- schedule according to student class timing + +This is where Timestable comes in. It improves two main areas: + +- querying + - student information + - parent information + - class timing +- data manipulation + - add + - edit + - delete + +By improving the data manipulation process, the tutor can organize student information more easily.
+By improving the querying process, the tutor can make scheduling less painstaking and time-consuming. + +
### 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…​ | +| -------- | -------------- | ----------------------------------------------------------- | --------------------------------------------------- | +| `* ` | Careless User | Be notified if there was a clash in timing | I can have peace of mind | +| `* ` | User | View schedule for a specific day | Can prepare for lesson and won't be absent | +| `* *` | User | Save class rates | Keep track of how much to charge each class | +| `* *` | User | Edit my student details | Keep track of changes of my students | +| `* *` | Organised user | Sort my students and classes | Arrange them the way I want to organise them | +| `* * *` | Forgetful user | Save their contacts | I can remember them | +| `* * *` | Organised User | View my class timings in a time table | Know how my weekly schedule looks like | +| `* * *` | User | Record parent contact of my students | Contact the student's parent in case of emergencies | +| `* * *` | User | Delete my student's contacts and information | I can declutter my contacts. | +| `* * *` | User | Record locations of classes | Knows where to go | +| `* * *` | User | Find a student or class with a specific name | Find details of a specific class or student quickly | + +
### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `TimesTable` and the **Actor** is the `user`, unless specified otherwise) -**Use case: Delete a person** +**Use case: Delete a student** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list students. +2. TimesTable shows a list of students. +3. User requests to delete a specific student in the list. +4. TimesTable deletes the student. Use case ends. **Extensions** -* 2a. The list is empty. +- 1a. The list is empty. Use case ends. -* 3a. The given index is invalid. +- 3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + - 3a1. TimesTable shows an error message. - Use case resumes at step 2. + Use case resumes at step 3. -*{More to be added}* +- 3b. The given delete command has a typo. + + - 3b1. TimesTable shows an error message. + + Use case resumes at step 3. + +**Use case: Add a student** + +**MSS** + +1. User input details of new person to be added to TimesTable. +2. TimesTable adds new person with relevant details. + + Use case ends. + +**Extension** + +- 1a. User keys in add command with invalid format(missing compulsory fields). + + - 1a1. TimesTable shows an error message. + + Use case resumes at step 1. + +- 1b. User keys in add command with valid format but invalid String format for certain fields. + + - 1b1. TimesTable shows an error message saying which field contains the invalid format. + + Use case resumes at step 1. + +**Use case: Edit student details** + +**MSS** + +1. User edits details of existing student. +2. TimesTable updates the student with the added details. + + Use case ends. + +**Extension** + +- 1a. User keys in field with invalid format. + + - 1a1. TimesTable shows an error message. + + Use case resumes at step 1. + +**Use case: Find student by name** + +**MSS** + +1. User input name of student that user wants to find. +2. TimesTable shows the student that has the same name. + + Use case ends. + +**Extension** + +- 1a. No student name matches the name keyword inputted. + + - 1a1. TimesTable shows no student listed. + + Use case ends. + +**Use case: Find student by tag** + +**MSS** + +1. User input tag of student that user wants to find. +2. TimesTable shows the student that has the same tag. + + Use case ends. + +**Extension** + +- 1a. No student tag matches the tag keyword inputted. + + - 1a1. TimesTable shows no student listed. + + Use case ends. + +**Use case: Add a tuition class** + +**MSS** + +1. User input details of new tuition class to be added to TimesTable. +2. TimesTable adds new tuition class with relevant details. + + Use case ends. + +**Extension** + +- 1a. User keys in addclass command with invalid format(missing compulsory fields). + + - 1a1. TimesTable shows an error message. + + Use case resumes at step 1. + +- 1b. User keys in addclass command with valid format but invalid String format for certain field. + + - 1b1. TimesTable shows an error message saying which field contains the invalid format. + + Use case resumes at step 1. + +- 1c. User keys in addclass command with valid format, valid String format for all fields, but class timing overlaps + with other classes already in TimesTable. + + - 1c1. TimesTable shows an error message saying that this operation would cause a clash in class timing. + + Use case resumes at step 1. + +**Use case: Delete a tuition class** + +**MSS** + +1. User requests to list classes. +2. TimesTable shows a list of classes. +3. User requests to delete a specific class in the list. +4. TimesTable deletes the class. + + Use case ends. + +**Extensions** + +- 1a. The list is empty. + + Use case ends. + +- 3a. The given index is invalid. + + - 3a1. TimesTable shows an error message. + + Use case resumes at step 3. + +- 3b. The given deleteclass command has a typo. + + - 3b1. TimesTable shows an error message. + + Use case resumes at step 3. + +**Use case: Add students to a tuition class** + +**MSS** + +1. User input index of class(in `classes` tab) to be added to and index of students(in `students` tab) to be added. +2. TimesTable adds students selected into class selected. + + Use case ends. + +**Extension** + +- 1a. User keys in addtoclass command with invalid index(student or class or both). + + - 1a1. TimesTable shows an error message citing which index is invalid. + + Use case resumes at step 1. + +- 1b. User attempts to add student to a class where the student is already in. + + - 1b1. TimesTable shows an error message saying that student is already in the class. + + Use case resumes at step 1. + +**Use case: Remove students from a tuition class** + +**MSS** + +1. User input index of class(in `classes` tab) to be added to and index of students(in `classes` tab) to be removed. +2. TimesTable removes students from the class selected. + + Use case ends. + +**Extension** + +- 1a. User keys in removefromclass command with invalid index(student or class or both). + + - 1a1. TimesTable shows an error message citing which index is invalid. + + Use case resumes at step 1. + +**Use case: Edit tuition class details** + +**MSS** + +1. User edits details of existing tuition class. +2. TimesTable updates the tuition class with the added details. + + Use case ends. + +**Extension** + +- 1a. User keys in field with invalid format(wrong field name). + + - 1a1. TimesTable shows an error message. + + Use case resumes at step 1. + +- 1b. User keys in invalid index. + + - 1b1. TimesTable shows an error message. + + Use case resumes at step 1. + +- 1c. User keys in class timing that overlaps with other class's class timing. + + - 1c1. TimesTable shows an error message saying that this operation would cause a clash in class timing. + + Use case resumes at step 1. + +**Use case: Find tuition class by class timing** + +**MSS** + +1. User input class timing of tuition class that user wants to find. +2. TimesTable shows the tuition classes that has similar class timing. + + Use case ends. + +**Extension** + +- 1a. No tuition class timing matches the class timing keyword inputted. + + - 1a1. TimesTable shows no tuition classes listed. + + Use case ends. + +**Use case: Find tuition class by name** + +**MSS** + +1. User input name of tuition class that user wants to find. +2. TimesTable shows the tuition class that has similar name. + + Use case ends. + +**Extension** + +- 1a. No tuition class name matches the name keyword inputted. + + - 1a1. TimesTable shows no tuition class listed. + + 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 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 1000 students without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -*{More to be added}* +4. The data should be stored locally in a human editable text file +5. Should not use a database management system to store data +6. Should follow OOP paradigm primarily +7. Should work without requiring an installer +8. Should not depend on a remote server +9. Project should simulate a brownfield project by evolving the code base with each iteration incrementally (breadth-first) +10. Should be for a single user +11. Third-party frameworks, libraries and services should be free, open-source, do not require any installation by the user, and do not violate other requirements. +12. GUI should work well for + - Standard screen resolutions of 1920x1080 and higher + - Screen scales 100% and 125% +13. GUI should be usable for + - Resolutions 1280x720 and higher + - Screen scales 150% +14. Should package everything into a single JAR file +15. Product should not exceed 100 MB and documents should not exceed 15 MB/file +16. Developer Guide and User Guide should be PDF-friendly ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +- **Mainstream OS**: Windows, Linux, Unix, OS-X +- **Private contact detail**: A contact detail that is not meant to be shared with others + +--- --------------------------------------------------------------------------------------------------------------------- +
## **Appendix: Instructions for manual testing** @@ -347,31 +1009,363 @@ testers are expected to do more *exploratory* testing. 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.
- Expected: The most recent window size and location is retained. - -1. _{ more test cases …​ }_ - -### Deleting a person - -1. Deleting a person while all persons are being shown - - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - - 1. 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: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. - -1. _{ more test cases …​ }_ - -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ + Expected: The most recent window size and location is retained. + +### Clearing data: `clear` + +1. Test case: Clear all `Student`s and `TuitionClass`es in TimesTable. + 1. Prerequisite: TimesTable contains `Student`s and `TuitionClass`es. Steps to do this are below: + 1. (You may skip this if you already have `Student`s) Add a student: `add n/Amber p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 2. (You may skip this if you already have `TuitionClass`es) Add a class: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 2. Clear all data: `clear` + 3. Expected: `TimesTable has been cleared!` and all `Student`s and `TuitionClass`es are removed from TimesTable. + +2. Test case: Clear empty TimesTable. + 1. Prerequisite: TimesTable does not contain `Student`s and `TuitionClass`es. Steps to do remove them are below: + 1. (You may skip this if you do not have `Student`s) Delete a student: `delete INDEX` for all `Student`s in TimesTable. + 2. (You may skip this if you do not have `TuitionClass`es) Delete a class: `deleteclass INDEX` for all `TuitionClass`es in TimesTable. + 2. Clear all data: `clear` + 3. Expected: `TimesTable has been cleared!` and all `Student`s and `TuitionClass`es are removed from TimesTable. + + +### Adding a Student: `add` +1. Test case 1: Add a `Student` successfully + 1. Add a `Student` to TimesTable: `add n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 + nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 2. Expected message: `New Student added: John Doe; Phone: 98765432; Email: johnd@example.com; Address: 311, Clementi Ave 2, #02-25; Tags: [Chemistry][Sec 3] + Next-of-Kin: Jack Doe; Phone: 10987654; Email: jackd@example.com; Address: 311, Clementi Ave 2, #02-25` +2. Test case 2: Cannot add duplicate `Student` + 1. Prerequisite: TimesTable contains the sample `Student`s. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Add a `Student` with a clash in `NAME` with an existing `Student`: `add n/Alex Yeoh p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 + nok/ n/Elise Yeoh p/10987654 e/eliseyeoh@gmail.com a/311, Clementi Ave 2, #02-25` + 3. Expected: `This person already exists in the address book` message shown. + + +### Adding a class: `addclass` + +1. Test case: Adding a `TuitionClass` successfully - no clash in `CLASS_TIMING` with existing `TuitionClass`es. + 1. Prerequisite: TimesTable does not have any `TuitionClass`es. If you have any `TuitionClass`, steps to remove them are below: + 1. (You may skip this if you do not have `TuitionClass`es) Delete a class: `deleteclass INDEX` for all `TuitionClass`es in TimesTable. + 2. Add a `TuitionClass`: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 3. Expected: `New class added: Class Timing: MON 11:30-13:30 Class Name: Sec 4 A Maths Location: Nex Tuition Center Rate: 70` and switched to `Classes` Tab. The `Classes` Tab now shows the `TuitionClass` you added. The `Timetable` Tab also shows your `TuitionClass` on `MON 11:30-13:30`. + +2. Test case: Adding a `TuitionClass` unsuccessfully - clash in `CLASS_TIMING` with existing `TuitionClass`. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Add a `TuitionClass` with a clash in `CLASS_TIMING` with an existing `TuitionClass`: `addclass cn/CS2103T ct/MON 10:30-12:30 r/70 l/Nex Tuition Center` + 3. Expected: `The operation aborted because it will introduce a clash in class timing.` and the `TuitionClass` does not get added to the `Classes` Tab. + +### Deleting a student: `delete` + +1. Test case: Delete a student successfully + 1. Prerequisites: Student to delete must be present. Steps to do this are below: + 1. Add student: `add n/Student p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 2. Note the students in the student list. + 2. Delete the student: `delete INDEX`. `INDEX` is the index of the student shown in the student list. If there were no other student present before, this would be 1. Otherwise, it would be the number shown at the top left corner of the student card. + 3. Expected: `Deleted Person: Student; Phone: 98765432; Email: johnd@example.com; Address: 311, Clementi Ave 2, #02-25; Tags: [Chemistry][Sec 3] Next-of-Kin: Jack Doe; Phone: 10987654; Email: jackd@example.com; Address: 311, Clementi Ave 2, #02-25` message shown. + Class no longer belongs in the class list noted in (ia). +1. Test case: Cannot delete a student that doesn't exist + 1. Prerequisites: Student at the student index to be deleted must not exist. + 1. Easiest way to ensure that this happens: run `clear` + 2. Note that the student list is empty. + 2. Delete the student at index 1: `deleteclass 1`. + 3. Expected: `The student index provided is invalid` message shown. + +### Deleting a tuition class: `deleteclass` + +1. Test case: Deleting a class successfully + + 1. Prerequisites: Class to delete must be present. Steps to do this are below: + 1. Add class: addclass cn/Sec 4 A Maths ct/FRI 11:30-13:30 r/70 l/Nex Tuition Center + 2. Note the classes in the class list. + + 2. Delete the class: `deleteclass INDEX`. `INDEX` is the index of the class shown in the class list. If there + were no other classes present before, this would be 1. Otherwise, it would be the number shown at the top left corner of the class card. + (eg. `deleteclass 1`). + + 3. Expected: `Class deleted: Class Timing: FRI 11:30-13:30 Class Name: Sec 4 A Maths Location: Nex Tuition + Center Rate: 70` message shown. Class no longer belongs in the class list noted in (ib). + +2. Test case: Cannot delete a class that does not exist + + 1. Prerequisites: Class at the class index to be deleted must not exist. Steps to do this are below: + 1. Easiest way to ensure that this happens: run clear + 2. Note that the class list is empty. + + 2. Delete the class at index 1: `deleteclass 1`.
+ 3. Expected: `The class index provided is invalid` message shown. + +### Adding a Student to a Class: `addtoclass` + +1. Test case 1: Add a `Student` to a `TuitionClass` successfully + 1. Prerequisites: You don't have any `Student`s or `TuitionClass`es. If you have at least _one_ `Student` or `TuitionClass`, you can skip adding a `Student` / `TuitionClass` respectively. + 2. Add a `Student` to TimesTable: `add n/Student p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 3. Add a `TuitionClass` to TimesTable: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 4. Add the `Student` to the `TuitionClass`: `addtoclass 1 1` + Expected: `Successfully added students to class` message shown +2. Test case 2: Cannot add to a class that does not exist + 1. Prerequisites: You don't have any classes. If you have any, you can remove them using the `deleteclass INDEX` command or run `clear`. + 2. If you already have a student, you can skip this step. + Add a student to TimesTable: `add n/Student p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 3. Add the student to non-existing class: `addtoclass 1 1` + Expected: `The class index provided is invalid` message shown +3. Test case 2: Cannot add to a student that does not exist to a class + 1. Prerequisites: You don't have any students. If you have any, you can remove them using the `delete INDEX` command or run `clear`. + 2. If you already have a class, you can skip this step. + Add a class to TimesTable: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 3. Add non-existing student to the class: `addtoclass 1 1` + Expected: `The student index provided is invalid` message shown + +### Removing Student(s) from a class : `removefromclass` + +1. Test case: Removing a student from a class that doesn't currently exist + 1. Prerequisites: Class at the class index must not exist. Steps to do this are below: + 1. Run command `listclass` and note the index of the last class. + 2. Remove students from non-existing class at index (max index + 1): `removefromclass 5 1 2 3 `.
+ Where last class is at index 4 from (ib). + 3. Expected: `The class index provided is invalid` message shown. + +2. Test case: Removing a student from a class that has no student + 1. Prerequisites: There must be a class with 0 students in it. Steps to do this are below: + 1. Clear TimesTable usiing: `clear`. + 2. Add a new class: `addclass cn/Sec 4 A Maths ct/FRI 11:30-13:30 r/70 l/Nex Tuition Center`. + 2. Remove student(s) from empty class: `removefromclass 1 1 2 3`. + 3. Expected: `The student index provided is invalid` message shown. + + +### Editing a student: `edit` + +1. Test case 1: Editing an existing student's own detail + 1. Prerequisites + 1. Add student: `add n/John p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` + 2. Note the student and his or her index in the displayed list in the `Student` tab. + 2. Edit the student (assuming the student's index is 1): `edit 1 n/updated student p/99993293 e/edited@gmail.com a/edited address t/` + 3. Expected: All fields are changed according to `edit` command inputs and all tags are removed. +2. Test case 2: Edit an existing student's NOK detail + 1. Prerequisites: Same as above + 2. Edit the student's NOK (assuming the student's index is 1): `edit 1 nok/ n/edited NOK name e/editedNOK@gmail.com a/edited address p/92393932` + 3. Expected: All NOK fields are changed according to `edit` command inputs +3. Negative test cases + 1. Out of range + 1. (assuming the student list does not have 100 students) enter `edit 100 n/edited name` + 2. Expected: "The student index provided is invalid" is displayed + 2. No fields provided + 1. enter `edit 1` + 2. Expected: "At least one field to edit must be provided." is displayed + 3. invalid prefix provided + 1. enter `edit 1 prefix/` + 2. Expected: Invalid command format message is displayed + +### Editing a class: `editclass` + +1. Test case 1: Editing an existing class + 1. Prerequisites + 1. Add a class: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 2. Note the class and its index in the displayed list in the `Class` tab + 2. Edit the class (assuming the class's index is 1): `editclass 1 cn/edited classname ct/MON 09:00-11:00 r/90 l/edited location` + 3. Expected: All fields are changed according to `editclass` command's inputs +2. Negative test cases: + 1. Out of range + 1. (assuming the class list does not have 100 classes) enter `editclass 100 cn/edited class name` + 2. Expected: "The class index provided is invalid" is displayed + 2. No fields provided + 1. enter `editclass 1` + 2. Expected: "At least one field to editclass must be provided." is displayed + 3. invalid prefix provided + 1. enter `editclass 1 prefix/` + 2. Expected: Invalid command format message is displayed + +### Sorting classes and students: `sort` + +1. Test case: Sort classes in ascending order. + 1. Prerequisites: Add more than 1 class in non-sorted order by timing. Steps to do this are below: + 1. Add later class first: `addclass cn/Sec 4 A Maths ct/FRI 11:30-13:30 r/70 l/Nex Tuition Center` + 2. Add earlier class second: `addclass cn/Sec 5 A Maths ct/FRI 10:30-11:30 r/70 l/Nex Tuition Center` + 3. Note that 'Sec 4 A Maths' comes _before_ 'Sec 5 A Maths' + 2. Sort classes: `sort timing asc`. + 3. Expected: `Sorted classes based on timing in asc direction` message shown, 'Sec 5 A Maths' now comes _before_ 'Sec 4 A Maths' in the class list. +2. Test case: Sort students in descending order. + 1. Prerequisites: Add more than 1 student in non-sorted order by name. Steps to do this are below: + 1. Add earlier student first: `add n/Amber p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25 ` + 2. Add later student second: `add n/Zebra p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/Chemistry t/Sec 3 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25 ` + 3. Note that 'Amber' comes _before_ 'Zebra' + 2. Sort students: `sort name desc`. + 3. Expected: `Sorted students based on name in desc direction` message shown, 'Zebra' now comes _before_ 'Amber' in the Student list. + + +### Locating a student by name: `findname` +1. Test case: No students has a name that matches the search term used + 1. Prerequisites: TimesTable contain multiple students. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using a name that currently do not exist: `findname Jennifer`. + 3. Expected: `0 persons listed!` message shown and no students shown in `Students` tab. + +2. Test case: A student is found with name that matches search term. + 1. Prerequisites: TimesTable contain multiple students. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using a single name that currently exists: `findname alex`. + 3. Expected: `1 persons listed!` message shown and 2 students shown in `Students` tab, namely `Alex Yeoh` and + `David Li`. +3. Test case: Multiple students have names that matches search term. + 1. Prerequisites: TimesTable contain multiple students. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using part of a name that currently exists: `findname li`. + 3. Expected: `3 persons listed!` message shown and 3 students shown in `Students` tab, namely + `Charlotte Oliveiro`, `David Li` and `Angelica Holcomb`. + +### Locating a class by class timing: `findclass` +1. Test case: No `TuitionClass` matches the search term used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_TIMING` of `WED`: `findclass WED` + 3. Expected: `0 classes listed!` and tab switched to `Classes` tab. + +2. Test case: `TuitionClass`es match the search term used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_TIMING` of 'MON': `findclass mon` + 3. Expected: `2 classes listed!` and tab switched to `Classes` tab. The `Classes` Tab contains 2 `TuitionClass`es with `CLASS_TIMING` of `MON`. + +3. Test case: `TuitionClass`es match the multiple search terms used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_TIMING` of `MON` and `11:30-13:30`: `findclass mon 11:30-13:30` + 3. Expected: `1 classes listed!` and tab switched to `Classes` tab. The `Classes` Tab contains 1 `TuitionClass` with `CLASS_TIMING` of `MON` and `11:30-13:30`. + +
+ +### Locating a class by name: `findclassname` + +1. Test case: No `TuitionClass` matches the search term used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_NAME` of `CS2103T`: `findclassname CS2103T` + 3. Expected: `0 classes listed!` and tab switched to `Classes` tab. + +2. Test case: `TuitionClass`es match the search term used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_NAME` of 'math': `findclassname math` + 3. Expected: `7 classes listed!` and tab switched to `Classes` tab. The `TuitionClass`es shown in the `Classes` Tab contains 7 `TuitionClass`es with `CLASS_NAME` of `math` (case insensitive). + +3. Test case: `TuitionClass`es match the multiple search terms used. + 1. Prerequisite: TimesTable contains the sample `TuitionClass`es. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Find `TuitionClass`es with `CLASS_NAME` of `math` and `jc`: `findclassname math, jc` + 3. Expected: `8 classes listed!` and tab switched to `Classes` tab. The `TuitionClass`es shown in the `Classes` Tab contains 8 `TuitionClass`es with `CLASS_NAME` of `math` or `jc` or both (case insensitive). + + +### Finding Student(s) by tag : `findtag` + +1. Test case: No students have tags that matches the search term used + 1. Prerequisites: TimesTable contain multiple students with differing tags. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using tag that currently do not exist: `findtag social studies`. + 3. Expected: `0 persons listed!` message shown and no students shown in `Students` tab. + +2. Test case: Students have tags that matches search term(single) + 1. Prerequisites: TimesTable contain multiple students with differing tags. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using a single tag that currently exists: `findtag a math`. + 3. Expected: `2 persons listed!` message shown and 2 students shown in `Students` tab, namely `Alex Yeoh` and + `David Li`. +3. Test case: Students have tags that matches search terms(multiple) + 1. Prerequisites: TimesTable contain multiple students with differing tags. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start off with the sample data in TimesTable. + 2. Find students using multiple tag that currently exists: `findtag a math, physic, sec 4`. + 3. Expected: `4 persons listed!` message shown and 2 students shown in `Students` tab, namely `Alex Yeoh`, + `Charlotte Oliveiro`, `David Li` and `Illana Page`. + +### Changing tabs: `view` + +1. Test case: View `Classes` tab, but command has a typo error + 1. View `Classes` tab with typo: `view clases`. + 2. Expected: `Invalid command format!This tab doesn't exists. You can only switch to students, timetable or + classes.` message show. + +2. Test case: View `Classes` tab, but user is already on `Classes` tab + 1. View `Classes` tab: `view classes`. + 2. Expected: `Successfully switched to CLASSES tab` and remains on `Classes` tab. + +3. Test case: View `Timetable` tab, user is on `Classes` tab + 1. View `Timetable` tab: `view timetable`. + 2. Expected: `Successfully switched to TIMETABLE tab` and switched to `TimeTable` tab. + +### Selecting a class: `class` +1. Test case 1: viewing an existing class + 1. Prerequisites + 1. Add a class: `addclass cn/Sec 4 A Maths ct/MON 11:30-13:30 r/70 l/Nex Tuition Center` + 2. Note the class and its index in the displayed list in the `Class` tab + 3. Adding students to the class (assuming the class's index is 1 and there are at least 2 students in Timestable): `addtoclass 1 1 2 ` + 2. view the class (assuming the class's index is 1): `class 1` + 3. Expected: the students list in `Classes` tab is showing students belonging to that class +2. Negative test cases: + 1. Out of range + 1. (assuming the class list does not have 100 classes) enter `class 100` + 2. Expected: "The class index provided is invalid" is displayed + +### Listing all students: `list` + +1. Test case: All `Student`s are listed and focus is moved to the `Students` tab. All previous filtering is reset. + 1. Prerequisite: `Student` filter must be present. Steps to do this are below: + 1. Delete `timestable.json` in the data file to start of with the sample data in TimesTable. + 2. Filter `Student`s: `findname alex`. One `Student` with the name `Alex Yeoh` should be listed. + 3. Go to `Classes` tab: `view classes` + 4. List all `Student`s: `list` + 5. Expected: `Listed all students` message shown and tab is changed to the `Students` tab. The list of `Student`s should be the same as before filtering (Step 2). + + +### Listing all classes: `listclass` + +1. Test case: All `TuitionClass`es are listed and focus is moved to `Classes` tab. All previous filtering is reset. + 1. Prerequisites: `TuitionClass` to filter must be present. Steps to do this are below: + 1. Add `TuitionClass`: `addclass cn/Sec 4 A Maths ct/FRI 11:30-13:30 r/70 l/Nex Tuition Center` + 2. Note the `TuitionClass`es in the class list. + 2. Filter classes: `findclassname hello`. No `TuitionClass`es should be listed. + 3. Go to `Students` tab: `view students` + 4. List `TuitionClass`es: `listclass` + 5. Expected: `Listed all classes` message shown and tab is moved to `Classes` tab. Class list noted in (ib) is shown. + +
+ +## Appendix: Effort + +The effort required to evolve `AB3` to `TimesTable` could be estimated to be approximately the effort required to create `AB3`. Our team have contributed roughly 15k lines of functional code, automated unit and integration testing, and documentation. + +### Addition of Tabs +* `AB3` did not have any different tabs. There was only one page showing the `Person`'s contacts. +* Addition of tabs was the start of the evolution from `AB3` into `TimesTable`. +* By adding tabs, we could implement different UIs into each tab to give the user a better experience. +* The effort to add tabs was not very high, as we only had to add an additional JavaFx control. +* Building the contents of the Tabs was the difficult portion as we had to design the entire layout of the tabs by ourselves. + +### Addition of Timetable Tab +* Adding the Timetable Tab was difficult, as it was our first time building an interface like this. +* However, we were inspired by [Pet Store Helper](#acknowledgements), and adapted some of their basic ui components for our application. +* Despite, adapting some of their basic ui components, the effort to build the Timetable Tab was still high, as we implemented the [building process](#timetable-tab-feature) from scratch. +* We had to account for many JavaFx issues, alignments and row and column spans to build the slots, which was relatively hard as it was something new to us. + +### Addition of Classes Tab +* Adding the Classes Tab was difficult as we had to design an entire user interface from the ground up to both contain details of the `TuitionClass`es in TimesTable and details of the `Student`s in each class. +* We faced exceptional difficulty when making sure that the details of the `TuitionClass`, such as size of the class, and the list of `Student`s in each class, was being updated when `Student`(s) were being added or removed from any particular class, or when any `Student`'s or `TuitionClass`'s details were being edited. +* The very design of the Classes Tab itself also necessitates a lot of coupling, because the view in the `StudentClassPanel` depended on the view in the `ClassListPanel` as it changes based on what was clicked. + +### Addition of new fields +* `AB3` initially had the `Name`, `Phone Number`, `Email`, `Address`, and `Tag` categories. +* For `TimesTable`, we added additional fields like `ClassTiming`, `ClassName`, `Rate`, `Location` and `Nok`. + +### Addition of Tuition Class +* While AB3 only tracks persons, TimesTable also keeps track of Tuition Classes. +* The addition of the `TuitionClass` required a great deal of effort as it brought a lot of problems at the start, such as `TuitionClass`es overlapping with one another when being added into the `UniqueClassList` that contained all `TuitionClass`es in TimesTable, creating a clash in class timing that was undesirable for our intended user's purpose. Another example is the problem of integrating `TuitionClass` and `Student` which is further discussed below. + + +### Addition of Commands +* TimesTable added many new commands: `addclass`, `deleteclass`, `addtoclass`, `removefromclass`, `editclass`, `findclass`, `findclassname`, `listclass`, `selectclass`, `findtag`, `sort` and `view` +* In addition, the `add` and `edit` commands were modified with the addition of TimesTable's new Next-of-Kin field. The `find` command was also modified to allow for multi-word and partial matches. +* Many of these commands required a lot of restructuring and addition of code. For instance, the class commands have to interact with both Tuition Classes and Students. + +### Integration of Classes and Students +* Integration of classes and students was difficult because it required `TuitionClass`es to store references to the `Student`s. The simple solution would be to just store a list of `Student` in each `TuitionClass` and store a list of `TuitionClass` in each `Student`. However, we quickly found out that it was not the ideal design.This is because it would increase the overall level of dependencies in the project drastically mainly due to the fact that `TuitionClass` and `Student` are so tightly coupled together. It also decreased the overall testability due to high dependency. +* Integration of classes and students was also cause storing and reading properly difficult due to their high degree of coupling and was very prone to error, resulting in a very large amounts of checks needed when reading from the data JSON file. +* The design that we eventually implemented was to use a list of `Name`s instead which effectively reduced overall dependency and the number of bugs that we had. It also reduced the number of lines in our codebase by hundreds which is great since we were able to achieve the same desired result with less code. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..77244c21177 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -23,7 +23,7 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. 1. **Verify the setup**: - 1. Run the `seedu.address.Main` and try a few commands. + 1. Run the `seedu.times.Main` and try a few commands. 1. [Run the tests](Testing.md) to ensure they all pass. -------------------------------------------------------------------------------------------------------------------- diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..2f80e71f618 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -29,8 +29,8 @@ There are two ways to run tests. This project has three types of tests: 1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest` + e.g. `seedu.times.commons.StringUtilTest` 1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest` + e.g. `seedu.times.storage.StorageManagerTest` 1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest` + e.g. `seedu.times.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..540aae89ecc 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,770 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +![Logo](images/TimestableLogo.png) +## Introduction + +**TimesTable** is a **tuition management** desktop app for tutors who prefer to use a Command Line Interface (CLI) over a +Graphical User Interface (GUI). **TimesTable** allows you to manage and keep track of your tuition students and classes. +The app stores basic information about your students and classes, such as `Name`, `Email`, `Address`, `Class Timing`, and more. +You can add the students to classes to organise them. + +**TimesTable** also automatically generates a TimeTable for you based on your created classes so you can visualise +your schedule at a glance. + +**TimesTable** uses the CLI to enter commands, which means you control the application by typing in commands into the +command box. Other GUI applications are controlled by clicking on buttons and boxes. +If you can type fast, TimesTable can get your class management tasks done faster than traditional GUI applications. +Using the CLI allows you to control the application quickly while still having the visual benefits of a GUI. + +This guide will take you through the commands of **TimesTable** step by step. For more information about how to use the +guide, head to the [Reading this User Guide](#reading-this-user-guide) section. To get started with using **TimesTable**, +jump to the [Quick Start](#quick-start) section. For a full list of commands and detailed instructions on each one, head to the +[Features](#features) section. + +
+ +## Table of Contents * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- -## Quick start +## Quick Start +### Set Up + +1. Ensure you have Java `11` or above installed in your Computer, if you do not, you can download it from [here](https://www.oracle.com/java/technologies/downloads/#java11-windows). + +2. Download the latest `timestable.jar` from [here](https://github.com/AY2122S1-CS2103T-F11-1/tp/releases). + ![DownloadJar](images/DownloadJar.png) +3. Copy the file to the folder you want to use as the _home folder_ for your TimesTable. +
-1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +4. For `Windows`, double-click the file to start the app. + For `Mac`, you need to open `TimesTable` using terminal. You can right-click on the folder and click on New Terminal at Folder to bring up your terminal, and key in `java -jar timestable.jar`. Press Enter to launch the application. + ![MacStartUp](images/TimestableMacStartUp.png) +
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+5. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample student data.
![Ui](images/Ui.png) + +
+ +### Tutorial + +1. Before we begin, note that we are at the Students Tab, where we show all the students that you are teaching. Other tabs will be explained later. +2. To get started, lets add a student using the [`add`](#adding-a-student-add) command. +First type `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 nok/ n/Jack Doe p/10987654 e/jackd@example.com a/311, Clementi Ave 2, #02-25` +into the command box to add 'John Doe' using the parameter `n/`, with his specified phone number using the `p/` parameter, and email using the `e/` parameter and so on (for more details refer to the [`add`](#adding-a-student-add) command). If you're wondering, the text after +`nok/` specifies all the information for his _next-of-kin_, and uses the similar parameters. +![TutorialImage1.png](images/UGCommandExamples/TutorialExample1.png) +Press enter afterwards to execute the command which adds a student to our TimesTable. +![TutorialImage2.png](images/UGCommandExamples/TutorialExample2.png) + +3. Next, we can try creating a class! Similar to before, we run the [`addclass`](#adding-a-class-addclass) command. +Type `addclass cn/Sec 4 A Maths ct/FRI 11:30-13:30 r/70 l/Nex Tuition Center` into the command box. +We are adding a class 'Sec 4 A Maths' using the `cn/` parameter, at 11:30am to 1:30pm using the `ct/` parameter, together with the rate and the location. +![TutorialImage3.png](images/UGCommandExamples/TutorialExample3.png) +Again, press enter to add the class to our TimesTable. + + +4. Oh! Notice that you have been transported to another tab. This is known as the Classes Tab, +which shows you all the information about the Classes you are teaching. +![TutorialImage4.png](images/UGCommandExamples/TutorialExample4.png) +When we scroll down, you can see that 'Sec 4 A Maths' is added at the bottom. + +5. See that blank space on the right? It is where all the students who are under that class go to. +I will now show you how to add students to a class! Here, we are using the [`addtoclass`](#adding-studentstudents-to-a-class-addtoclass) +command, which takes in the index of the class, followed by the indexes of the students. +Type `addtoclass 13 10` to add the student that you just added (in index 10) to the class that you just added (in index 13). +As shown, you can see the students that are added to the class. How cool is that? +![TutorialImage5.png](images/UGCommandExamples/TutorialExample5.png) + +6. Continuing on, let me introduce you to the Timetable tab! Here, we are using the [`view`](#viewing-of-different-tabs-view) command. +Type `view timetable` and press enter. +![TutorialImage6.png](images/UGCommandExamples/TutorialExample6.png) +Here, you can see all the classes that you are teaching in a nice, visual, weekly timetable format! On the first row which is Monday, you can see the 'Sec 4 A Math' class that you just added, with the correct timing. + + +7. Now that you have experienced the core functionality, you are all ready to explore the other commands. +To delete, we have commands like [`delete`](#deleting-a-student--delete) and [`deleteclass`](#delete-a-class-deleteclass) for students and classes respectively, and even commands to [`find`](#locating-students-by-name-findname) and [`sort`](#sorting-students-and-classes-sort) classes and students when you have become more familiar with the app, just refer to the [Features](#features) below for details of each command. +Lastly, if you would like to fill in Timestable with your own students, simply use the [`clear`](#clearing-all-entries--clear) command to delete all the sample students and classes. + +
+ +## Reading this User Guide +### General Symbols and Syntax + +Syntax | Definition +--------|------------------ +`UPPER_CASE` | Words in `UPPER_CASE` are the inputs to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is an input which can be used as `add n/John Doe`. +`a/` | Signifies a field. The user inputs the field after the signifier. Also known as a parameter. (see [Glossary](#glossary)) +`[a/UPPER_CASE]` | Items in square brackets refer to optional fields.
e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or just `n/John Doe`. +`...` | Indicates that the preceding field can be used multiple times.
e.g. `[t/TAG]…​` can be used multiple times, such as`t/friend` or `t/friend t/family`. -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: +### Glossary - * **`list`** : Lists all contacts. +Term | Definition +--------|------------------ +NOK | Next-of-kin. Refers to the student's guardian, parent or perhaps close friend to be contacted regarding admin matters like payment. +PARAMETERS | The inputs before the `/` are known as _parameters_.
e.g `n/NAME` (`n/` is the parameter for name).
e.g `a/ADDRESS` (`a/` is the parameter for name). +INDEX | The number next to the Student/Class that shows its postion on the list. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +
- * **`clear`** : Deletes all contacts. +## Features - * **`exit`** : Exits the app. +
:bulb: **Tip:** Parameters can be in any order.
+ e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +
-1. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- +### Student commands -## Features +#### Adding a student: `add` +
-
+Adds a student to the TimesTable. -**:information_source: Notes about the command format:**
+Format: +`add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]… nok/ n/NOK_NAME p/NOK_PHONE_NUMBER e/NOK_EMAIL a/NOK_ADDRESS` -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* This is a command that requires next-of-kin (NOK) information. +* This command is split into two segments (excluding command keyword). The first segment are the inputs before + `nok/` and the second segment are the inputs after `nok/`. + * Inputs in the first segment are about student information whereas inputs in the second segment are about NOK's information. +* The order of input within its own segment is swappable, but the segments themselves are not. +* The command does not allow adding duplicate students - as defined as the student having the same name, ignoring case. +* `NAME` can have a maximum of 120 characters. +* `PHONE_NUMBER` has to be between 3-25 numbers. +* `TAG` can have a maximum of 15 characters per tag. -* 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`. +
:bulb: **Tip:** +A student can have 0 to 5 tags. +
-* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +Examples: -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +1. `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/ALevels nok/ n/Mary Doe p/93334848 e/mary23@gmail.com a/311, Clementi Ave 2, #02-25` +Adds a student with `NAME` `John Doe`, `PHONE` `98765432`, `EMAIL` `johnd@example.com`, `ADDRESS` `John street, block 123, #01-01`, `TAG` `ALevels`, with next-of-kin with `NAME` `Mary Doe`, `PHONE` `93334848`, `EMAIL` `mary23@gmail.com`, `ADDRESS` `311, Clementi Ave 2, #02-25` -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +2. `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/slow learner nok/ n/Karen e/karenSUper@gmail.com p/99994444 a/311, Clementi Ave 2, #02-25` + Adds a student with `NAME` `Betsy Crowe `, `PHONE` `1234567 `, `EMAIL` `betsycrowe@example.com`, `ADDRESS` `Newgate Prison `, `TAG` `friend`, with next-of-kin with `NAME` `Karen`, `PHONE` `99994444 `, `EMAIL` `karenSUper@gmail.com `, `ADDRESS` `311, Clementi Ave 2, #02-25` -
+ ![AddCommandExample](images/UGCommandExamples/AddCommand.png) +

+Example 1: Add John Doe +

-### Viewing help : `help` +
-Shows a message explaning how to access the help page. +#### Editing a student : `edit` +
-![help message](images/helpMessage.png) +Edits an existing student in the TimesTable. -Format: `help` +Format: +`edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​ [nok/ [n/NOK_NAME] [p/NOK_PHONE] [e/NOK_EMAIL] [a/NOK_ADDRESS]]` +` + +* Edits the student at the specified `INDEX`. The index refers to the index number shown in the displayed student + list. The index **must be a positive integer** 1, 2, 3, …​ +* At least one of the optional fields must be provided. +* An optional `nok/` (next-of-kin) field can be provided to edit the student's next-of-kin (NOK). All fields that come after `nok/` + will be for the student's next-of-kin. (same rule from `add` command applies) + * If `nok/` is provided, at least one of the optional fields belonging to NOK must be provided. + * Inputs in the first segment are about student information whereas inputs in the second segment are about NOK's information. +* The order of input within its own segment is swappable, but the segments themselves are not. +* When editing tags, the existing tags of the student will be removed i.e adding of tags is not cumulative. + * You can remove all the student's tags by typing `t/` without + specifying any tags after it. +* `NAME` can have a maximum of 120 characters. +* `PHONE_NUMBER` has to be between 3-25 numbers. +* `TAG` can have a maximum of 15 characters per tag, with a maximum of 5 tags. + +Examples (editing student information only): +1. `edit 1 p/91234567 e/johndoe@example.com` Edits the `PHONE` and `EMAIL` of the 1st student to be `91234567` and `johndoe@example.com` respectively. +2. `edit 2 n/Betsy Crower t/` Edits the `NAME` of the 2nd student to be `Betsy Crower` and clears all existing `TAG`s. +3. `edit 1 n/kevin p/12345678` Edits the `NAME` and `PHONE` of student 1 to become `kevin` and `12345678`. +4. `edit 4 n/John Walker a/4 Petir Road #16-04 Singapore 657891` Edits the `NAME` and `ADDRESS` of the 4th person to be + `John Walker` and `4 Petir Road #16-04 Singapore 657891` respectively. + +![EditCommandExample1](images/UGCommandExamples/EditCommand1.png) +

+Example 1: Edit Student 1 +

+ +Examples (also editing nok information): +1. `edit 2 nok/ p/98429239` Edits 2nd student's NOK's `PHONE` to be `98429239`. +2. `edit 3 a/Com2 nok/ p/98429239` Edits 3rd student's `ADDRESS` to be `Com2` while also editing + NOK's `PHONE` to be `98429239`. + +![EditCommandExample2](images/UGCommandExamples/EditCommand2.png) +

+Example 1: Edit Student 2 with next-of-kin information +

+ +
+ +#### Locating students by name: `findname` +
+ +Finds students whose `NAME` contain any of the given keywords. +Note that if you want to display the entire list of students again, run `list`. + +Format: +`findname NAME [, [NAME]...]` + +* The search is case-insensitive. e.g. `hans` will match `Hans`. +* The search terms are split by commas. e.g. `findname alex lim, bernice yu` +* Only the student's `NAME` is searched. +* Partial matches will still be matched e.g. `Han` will match `Hans`. +* Persons matching at least one search term will be returned (i.e. `OR` search). + e.g. `findname alex lim, bernice yu` will return `Alex Lim`, `Bernice Yu`. +* The entire search term is used for matching e.g. `findname Alex L` will match `Alex Lim` + but not `Alex Yu` + +
+Examples: +1. `findname John` returns `john` and `John Doe` in both `Students` and `Classes` tab. +2. `findname alex, david` returns `Alex Yeoh`, `David Li` in both `Students` and `Classes` tab.
-### Adding a person: `add` +![FindnNameCommandExample](images/UGCommandExamples/FindNameCommand.png) +

+Example 1: Find Students with name John +

-Adds a person to the address book. +
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +**:information_source: Note:**
-
:bulb: **Tip:** -A person can have any number of tags (including 0) +* For commands that alters the list of students (eg. `findname`, `findtag`, `sort name asc`), the displayed + changes for students will be shown in both the `Students` tab as well as the `Classes` tab. + This means that when students are filtered by their `name` and `tag`, they will be filtered by their `name` and `tag` in the `Classes` tab as well. + Likewise, when students are sorted by their names, they will be sorted in the `Classes` tab as well. + +* The `list` and `listclass` commands can be used to show the original lists of students and classes respectively. + +* Class size will **not** be affected by filtering students (using FindName or FindTag).
+
+ +#### Locating students by tag: `findtag` +
+ +Finds students whose `TAG`s contain any of the given keywords. +Note that if you want to display the entire list of students again, run `list`. + +Format: +`findtag KEYWORD [, [KEYWORD]...]` + +* Search terms can partially match the tag, or the entire tag, e.g. `math` for all `A Math` and `C Math` tags, or `A Math` for the `A Math` tag. +* Search terms are separated by commas. e.g. `findtag math, physics` will find students + with tags containing `math` or `physics`. +* Students matching at least one search term will be returned (i.e. `OR` search). + e.g. `findtag math, physics` will return students with the `Math` `TAG` but no `Physics` `TAG`, + students with only the`Physics` `TAG` but no `Math` `TAG`, and students with both `TAG`s. +* The search is case-insensitive. e.g `math` will match `Math`. + 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` +1. `findtag math` returns `Alex Yeoh` with the `A Math` `TAG` and `John Doe` with the `C Math` `TAG` in both `Students` and `Classes` tab. +2. `findtag math, physics` returns `Alex Yeoh` with the `A Math` and `Biology` `TAG`s in both `Students` and `Classes` tab. +
+ +![FindTagCommandExample](images/UGCommandExamples/FindTagCommand.png) +

+Example 1: Find Students with tag math +

+ + +
+ +**:information_source: Note:**
+ +* For commands that alters the list of students (eg. `findname`, `findtag`, `sort name asc`), the displayed + changes for students will be shown in both the `Students` tab as well as the `Classes` tab. + This means that when students are filtered by their `name` and `tag`, they will be filtered by their `name` and `tag` in the `Classes` tab as well. + Likewise, when students are sorted by their names, they will be sorted in the `Classes` tab as well. -### Listing all persons : `list` +* The `list` and `listclass` commands can be used to show the original lists of students and classes respectively. -Shows a list of all persons in the address book. +* Class size will **not** be affected by filtering students (using FindName or FindTag). +
+
+ +#### Listing all students : `list` +
+ +Shows a list of all students in the Students tab. Format: `list` -### Editing a person : `edit` +![ListCommandExample](images/UGCommandExamples/ListCommand.png) + +
+ + +#### Deleting a student : `delete` +
+ +Deletes the specified student from the TimesTable. + +Format: +`delete INDEX` +* Deletes the student at the specified `INDEX`. +* The index refers to the index number shown in the displayed student list in the `Students` tab. + +Examples: +1. `list` followed by `delete 2` deletes the 2nd student in the TimesTable. +2. `findname Betsy` followed by `delete 1` deletes the 1st student in the results of the `find` command. + +![DeleteCommandExample](images/UGCommandExamples/DeleteCommand.png) +

+Example 1: Deletes the 2nd student +

+ +
+ +### Class commands + +#### Adding a class: `addclass` +
+ +Add a class to the TimesTable. + +Format: +`addclass cn/CLASS_NAME ct/CLASS_TIMING r/HOURLY_RATE l/LOCATION` + +* This command adds a new class to keep track of all classes that the user is teaching. +* `CLASS_TIMING` must be in the form `ct/DAY HH:MM-HH:MM` +* `DAY` is case insensitive. +* `CLASS_TIMING` can only start and end at the hour mark or half hour mark, but can also end at 23:59 hours. +* `RATE` must be less than $1,000,000/hr + +Examples: +1. `addclass cn/CS2103T ct/MON 09:30-11:30 r/70 l/Nex Tuition Center` +Adds a new class with name `CS2103T`, with class timing `MON 09:30-11:30`, with hourly rate of $`70`, at `Nex +Tuition Center`. + +2. `addclass cn/Sec 4 E Maths ct/TUE 12:30-14:30 r/65 l/Block 123, Clementi Ave 6, #14-41` +Adds a new class with name `Sec 4 E Maths`, with class timing `Tue 12:30-14:30`, with hourly rate of $`65`, at `Block 123, Clementi Ave 6, #14-41`. + +![AddClassCommandExample](images/UGCommandExamples/AddClassCommand.png) +

+Example 1: Adding CS2103T class +

+ +
-Edits an existing person in the address book. +#### Editing a class: `editclass` +
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Edits an existing class in the class list in the `classes` tab. -* 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, …​ +Format: +`editclass 1 [cn/CLASS_NAME] [ct/CLASS_TIMING] [r/RATE] [l/LOCATION]` + +* Edits the class at the specified `INDEX`. The index refers to the index number shown in the displayed class + list in the `classes` tab. + * The index **must be a positive integer** 1, 2, 3, …​ + * The index must belong to a class. * 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. +* `CLASS_TIMING` can only start and end at the hour mark or half hour mark, but can also end at 23:59 hours. +* Edit commands that will create a clash of `CLASS_TIMING` with other classes is not accepted. +* `RATE` must be less than $1,000,000/hr + +
+ +Examples: +1. `editclass 1 ct/wed 15:00-17:00` Edits the first class in the class list's `CLASS_TIMIMG` to be on Wednesday from +3pm to 5pm. + +![EditClassCommandExample](images/UGCommandExamples/EditClassCommand.png) +

+Example 1: Editing class indexed 1 +

+ +
+ +#### Adding student/students to a class: `addtoclass` +
+ +Add a single or multiple students to an existing class. + +Format: +`addtoclass CLASS_INDEX STUDENT_INDEX...` + +* This command adds any number of existing students into an existing class. +* `CLASS_INDEX` is the index number of the class in the displayed class list in the `Classes` tab, which will be + receiving the new students. +* `STUDENT_INDEX...` are the index number/s of the students shown in the displayed student list in the `Students` tab, these students are + to be added into the class. +* Exactly one class index must be provided and at least one student index must be provided. +* Students that already exist in the class can't be added to the same class. +* If you enter duplicate student indices in one command, Timestable will only add the student once. +* Size of the class will change to reflect the number of students in the class. + +
+ +Example: +1. `addtoclass 13 1 2 3` +Adds the 1st, 2nd and 3rd student in the displayed student list in the `Students` tab into the 13th class in the +displayed class list in the `Classes` tab, `size` of the class will increase by 3. + +![AddToClassCommandExample](images/UGCommandExamples/AddToClassCommand.png) +

+Example 1: Adding students indexed 1, 2 and 3 to class indexed 13 +

+ +
+ +#### Removing students from a class: `removefromclass` +
+ +Removes a single or multiple students from an existing class. + +Format: +`removefromclass CLASS_INDEX STUDENT_INDEX...` + +* Removes a non-zero number of existing students from an existing class. +* `CLASS_INDEX` is the index number of the class in the displayed class list in the `Classes` tab to have its students removed from. +* `STUDENT_INDEX...` are the index number(s) of the students, shown in the displayed student list of the class to be removed from + in the `Classes` tab. + + +Example: +1. `removefromclass 13 1 2 3` +Removes the 1st, 2nd and 3rd student in the displayed student list of the 13th class in the `classes` tab, causing the +`size` of 1st class to decrease by 3. + +![RemoveFromClassCommandExample](images/UGCommandExamples/RemoveFromClassCommand.png) +

+Example 1: Remove students indexed 1, 2 and 3 in class list from class indexed 13 +

+ +
+ +#### Locating class by class timing : `findclass` +
+ +Finds a class whose `CLASS_TIMING` matches the given keyword. +Note that if you want to display the list of classes again, run `listclass`. + +Format: `findclass CLASS_TIMING` + + +* The valid keywords for `CLASS_TIMING` are limited to the following types: + 1. 3 letter abbreviation for day of the week e.g. `Mon`, `Tue`, etc. + 2. Time expressed in HH:MM-HH:MM format e.g. `11:30-12:30`, `15:00-16:00`, etc. + * `CLASS_TIMING` can only start and end at the hour mark or half hour mark, but can also end at 23:59 hours. +* Either a single keyword or two keywords of different types should be provided otherwise no classes would be returned. +* Multiple keywords of the same type (eg Mon Tue) would not return any classes, because the command finds classes which contain both timings (Mon and Tue), and + it is currently not possible to have a class with two different timings (ie a class that occurs on both Monday and Tuesday or both `10:00-12:00` and `17:00-19:00`) + * Important clarification: In TimesTable, class refers to a single slot per week in the timetable. +* If two keywords are entered, then the class returned would be the one that match all the keywords + (see example below). + + +Examples: +1. Single keyword + 1.1. `findclass mon` returns all classes on Monday. + 1.2. `findclass 10:00-12:00` returns all classes scheduled for `10:00 to 12:00` no matter which day of the week it belongs + to.![FindClassCommandExample](images/UGCommandExamples/FindClassCommand1.png) +

Example 1.1: Find all classes on Monday

+2. Two keywords + 2.1 `findclass mon 11:30-13:30` returns the exact class on `Mon at 11:30-13:30`. + 2.2 `findclass tue 11:00-12:00` returns the exact class on `Tue at 11:00-12:00`. ![FindClassCommandExample](images/UGCommandExamples/FindClassCommand2.png) +

Example 2.1: Find classes on Monday at 11:30-13:30

+3. Negative examples (Two or more keywords of the same type) + 3.1 `findclass mon tue` returns nothing. + 3.2 `findclass 09:00-10:30 11:00-12:00` returns nothing. + +![FindClassCommandExample](images/UGCommandExamples/FindClassCommand3.png) +

+Example 3.1: Find classes with two keywords of the same type +

+ + +
+ +#### Locating class by class name: `findclassname` +
+ +Finds a class whose class name matches the given keywords. +Note that if you want to display the list of classes again, run `listclass`. + +Format: +`findclassname CLASS_NAME [, [CLASS_NAME]...]` + +* The search is case-insensitive `PHYSICS` will match 'physics'. +* The search terms are split by commas. e.g. `findclassname sec 4 physics, jc math` +* Only the class' `CLASS_NAME` is searched. +* Partial matches will still be matched e.g. `Phy` will match `Physics`. +* Classes matching at least one search term will be returned (i.e. `OR` search). + e.g. `findclassname sec 4 phy, jc math` will return `sec 4 physics`, `jc mathematics`. +* The entire search term is used for matching e.g. `findclassname sec 4 phy` will match `sec 4 physics` + but not `sec 4 maths` + +Examples: +1. `findclassname math` returns all the classes with math in the class name. +2. `findclassname Sec, 4, maths` returns all the classes with `sec` or `4` or `maths` in the class name. Hence, + class with name `sec 4 physics` and class with name `JC maths` would both be returned. + +![FindClassNameCommandExample](images/UGCommandExamples/FindClassNameCommand.png) +

+Example 1: Find classes with math in the class name +

+ +
+ +#### Listing all the classes: `listclass` +
+ +Shows a list of all classes in the Classes tab. + +Format: `listclass` + +![ListClassCommandExample](images/UGCommandExamples/ListClassCommand.png) + +
+ +#### Selecting of classes: `class` +
+ +Selects a class in the Classes tab and displays its students without the need to use the mouse to double click the respective class. + +Format: +`class CLASS_INDEX` + +* Selected class will not be highlighted in the same way as when you click on a class using the mouse. +* `CLASS_INDEX` must be an index of a class that exists in the displayed class list. 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. +* `class 1` selects the class with `CLASS_INDEX` of `1` and displays its students in the Classes tab. + +![SelectClassCommandExample](images/UGCommandExamples/ClassCommand.png) -### Locating persons by name: `find` +
-Finds persons whose names contain any of the given keywords. +#### Delete a class: `deleteclass` +
-Format: `find KEYWORD [MORE_KEYWORDS]` +Deletes the specified class from the TimesTable. -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +Format: +`deleteclass INDEX` + +* Deletes the class at the specified `INDEX`. +* The index refers to the index number shown in the displayed class list in the `Classes` tab. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `listclass` followed by `deleteclass 2` deletes the 2nd class in the TimesTable. + +![DeleteClassCommandExample](images/UGCommandExamples/DeleteClassCommand.png) + +
+ +### General commands + +#### Sorting students and classes: `sort` +
+ +Sorts the students based on their `NAME` in alphabetical order, +or classes based on their `CLASS_TIMING`, in either ascending or descending order. + +Format: +`sort PARAMETER_TO_SORT_BY DIRECTION_OF_SORT` + +* `PARAMETER_TO_SORT_BY` can either be `name` or `timing` which sorts the students and classes respectively. +* `DIRECTION_OF_SORT` can either be `asc` or `desc` to represent ascending and descending respectively. +* Sorting by `name` sorts the students in the Students tab and Classes tab but sorting by `timing` only sorts the classes in Classes tab but not the students in Students tab. + +Examples: +1. `sort name asc` sorts students alphabetically by their `NAME` in ascending order. +2. `sort name desc` sorts students alphabetically by their `NAME` in descending order. +3. `sort timing asc` sorts classes based on their `CLASS_TIMING` starting from the earliest in the week to the latest. +4. `sort timing desc` sorts classes based on their `CLASS_TIMING` starting from the latest in the week to the earliest. + +![SortCommandExample](images/UGCommandExamples/SortCommand.png) +

+Example 1: Sorts students in alphabetical order +

-### Deleting a person : `delete` -Deletes the specified person from the address book. +
+ +**:information_source: Note:**
+ +* For commands that alters the list of students (eg. `findname`, `findtag`, `sort name asc`), the displayed + changes for students will be shown in both the `Students` tab as well as the `Classes` tab. + This means that when students are filtered by their `name` and `tag`, they will be filtered by their `name` and `tag` in the `Classes` tab as well. + Likewise, when students are sorted by their names, they will be sorted in the `Classes` tab as well. + +* The `list` and `listclass` commands can be used to show the original lists of students and classes respectively. + +* Class size will **not** be affected by filtering students (using FindName or FindTag). +
+ +
+ +#### Viewing of different tabs: `view` +
-Format: `delete INDEX` +Views an existing tab in the TimesTable without the need to use the mouse to click. -* 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, …​ +Format: +`view TAB_TO_VIEW` + +* `TAB_TO_VIEW` has to be an existing tab in Timestable (`students`, `classes`, `timetable`). 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. +1. `view timetable` switches the displayed tab to be the `timetable` tab. + +![ViewCommandExample](images/UGCommandExamples/ViewCommand.png) -### Clearing all entries : `clear` +#### Clearing all entries : `clear` +
-Clears all entries from the address book. +Clears all entries from the TimesTable. Format: `clear` -### Exiting the program : `exit` +![ClearCommandExample](images/UGCommandExamples/ClearCommand.png) + +#### Viewing help : `help` +
+ +Shows a message explaining how to access the help page. + +Format: `help` + +![help message](images/UGCommandExamples/HelpCommand.png) + +#### Exiting the program : `exit` +
Exits the program. Format: `exit` -### Saving the data +#### 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. +TimesTable data is saved in the hard disk automatically in the `data` folder present in the same directory as `timestable.jar` after any command that changes the data. There is no need to save manually. -### Editing the data file +#### Editing the data file +
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +TimesTable data are saved as a JSON file `[JAR file location]/data/timestable.json`. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. +If your changes to the data file makes its format invalid, Timestable will discard all data and start with an empty data file at the next run.
-### Archiving data files `[coming in v2.0]` - -_Details coming soon ..._ - -------------------------------------------------------------------------------------------------------------------- ## 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 Timestable home folder. + +**Q**: Are you going to add XXX feature? What features are you planning to add?
+**A**: I'm glad you asked! We're currently planning to add the following features: + +
+ +### Upcoming Features: + +#### Payment Management with Invoice + +Different classes you teach will have different hourly rates, and they will of course have varying lengths and frequencies. +Having to keep track of how much someone has to pay and whether they have paid is a major source of stress. + +We recognise this demand, and thus are working on this feature for you to easily track required payments to students. + +A work in progress sneak peak of the feature is shown below. + +![UpcomingFeature](images/UpcomingFeature.png) + +After a class, just input the time taught for that class and mark the attendance of those present. + +For each student present, TimesTable will take the hourly rate multiplied by the +session length for each person present to calculate the amount that each student has to pay. + +The session length field is useful when the class length goes beyond the intended length or when the class cuts short. + +This information will then be consolidated in an invoice to be generated. + +When you want to generate a new invoice, simply use the 'createinvoice' command. Invoices will be generated for all +students of the selected class. These invoices will use the session attendances recorded since the last invoice, generating +a table with the relevant information such as session date, session length, hourly rate, student name, and so on. You will +also be able to set the due date for the invoice. + +An example of the invoice generated is shown below. + +![InvoiceExample](images/InvoiceExample.png) + +With this invoice generation feature, you will easily be able to charge the correct amount to your clients +and you would also not have to worry about providing evidence. TimesTable will take care of it for you. + +In addition to invoice generation, we are also working on an invoice management feature that will allow you to track +the status of each invoice. You will be able to mark the invoices as paid, and archive them. TimesTable will also +alert you once the due date for any invoice has passed. You can simply let TimesTable keep track of all payments for you. + + +If there are any other proposed features, please submit an issue via our GitHub over [here](https://github.com/AY2122S1-CS2103T-F11-1/tp/issues) and we'll look into it! -------------------------------------------------------------------------------------------------------------------- +
## 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` +### Student Commands Summary + +Action | Format, Examples| +--------|------------------| +**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]… nok/ n/NOK_NAME p/NOK_PHONE_NUMBER e/NOK_EMAIL a/NOK_ADDRESS`
e.g., `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/ALevels nok/ n/Mary Doe p/93334848 e/mary23@gmail.com a/311, Clementi Ave 2, #02-25 `| +**Delete** | `delete INDEX`
e.g., `delete 3`| +**Edit** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​ [nok/ [n/NOK_NAME] [p/NOK_PHONE] [e/NOK_EMAIL] [a/NOK_ADDRESS]]`
e.g.,`edit 2 n/James Lee e/jameslee@example.com`| +**Find name** | `findname NAME [, [NAME]...]`
e.g., `find Stuart`| +**Find tag** | `findtag KEYWORD [, [KEYWORD]...]`
e.g., `findtag math, physics`| +**List** | `list`| +**Sort** | `sort PARAMETER_TO_SORT_BY DIRECTION_OF_SORT`
e.g., `sort name asc`| + +
+ +### Class Commands Summary + +Action | Format, Examples| +--------|------------------| +**Add class** | `addclass cn/CLASS_NAME ct/CLASS_TIMING r/HOURLY_RATE l/LOCATION`
e.g., `addclass cn/Sec 4 A Maths ct/mon 11:30-13:30 r/70 l/Nex Tuition Center`| +**Add to class** | `addtoclass CLASS_INDEX STUDENT_INDEX…`
e.g., `addtoclass 1 1 2 3`| +**Delete class** | `deleteclass INDEX`
e.g., `deleteclass 2`| +**Edit class** | `editclass 1 [cn/CLASS_NAME] [ct/CLASS_TIMING] [r/RATE] [l/LOCATION]`
e.g., `editclass 1 ct/wed 15:00-17:00`| +**Find class name** | `findclassname CLASS_NAME [, [CLASS_NAME]...]`
e.g., `findclassname math`| +**Find class timing** | `findclass CLASS_TIMING`
e.g., `findclass mon 11:00-12:00`| +**List class** | `listclass`| +**Remove from class** | `removefromclass CLASS_INDEX STUDENT_INDEX...`
e.g., `removefromclass 1 1 2 3`| +**Select class** | `class CLASS_INDEX`
e.g., `class 2`| +**Sort** | `sort PARAMETER_TO_SORT_BY DIRECTION_OF_SORT`
e.g., `sort timing asc`| + +
+ +### General Commands Summary + +Action | Format, Examples| +--------|------------------| +**Clear** | `clear`| +**Exit** | `exit`| +**Help** | `help`| +**View** | `view TAB_TO_VIEW`
e.g., `view timetable`| + diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..e8033f05b1d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TimesTable" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S1-CS2103T-F11-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..75676f22a40 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "TimesTable"; font-size: 32px; } } diff --git a/docs/diagrams/AddClassModelSequenceDiagram.puml b/docs/diagrams/AddClassModelSequenceDiagram.puml new file mode 100644 index 00000000000..dbcba75a96a --- /dev/null +++ b/docs/diagrams/AddClassModelSequenceDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":TimesTable" as TimesTable MODEL_COLOR_T3 +participant ":UniqueClassList" as UniqueClassList MODEL_COLOR_T3 +participant "internalList:ObservableArrayList" as ObservableArrayList MODEL_COLOR_T3 +end box + +[-> Model : addTuitionClass(t) +activate Model + +Model -> TimesTable : addTuitionClass(t) +activate TimesTable + +TimesTable -> UniqueClassList : add(t) +activate UniqueClassList + +UniqueClassList -> UniqueClassList : isValidTiming(t) +activate UniqueClassList +UniqueClassList --> UniqueClassList +deactivate UniqueClassList + +UniqueClassList -> ObservableArrayList : add(t) +activate ObservableArrayList + + +@enduml diff --git a/docs/diagrams/AddClassRef1.puml b/docs/diagrams/AddClassRef1.puml new file mode 100644 index 00000000000..f4d47533b93 --- /dev/null +++ b/docs/diagrams/AddClassRef1.puml @@ -0,0 +1,31 @@ +@startuml +!include style.puml + +group sd generate StudentNameList and TuitionClass + +box Logic LOGIC_COLOR_T1 +participant ":AddClassCommandParser" as AddClassCommandParser LOGIC_COLOR +participant "s:StudentNameList" as StudentNameList LOGIC_COLOR +participant "t:TuitionClass" as TuitionClass LOGIC_COLOR +end box + +activate AddClassCommandParser +create StudentNameList +AddClassCommandParser -> StudentNameList : StudentNameList() +activate StudentNameList + +StudentNameList --> AddClassCommandParser : s +deactivate StudentNameList + +create TuitionClass +AddClassCommandParser -> TuitionClass : TuitionClass(className, classTiming, location, rate, s) +activate TuitionClass + +TuitionClass --> AddClassCommandParser : t +deactivate TuitionClass + + + + +end +@enduml diff --git a/docs/diagrams/AddClassSequenceDiagram.puml b/docs/diagrams/AddClassSequenceDiagram.puml new file mode 100644 index 00000000000..62f2578b557 --- /dev/null +++ b/docs/diagrams/AddClassSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":AddClassCommandParser" as AddClassCommandParser LOGIC_COLOR +participant "a:AddClassCommand" as AddClassCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("addclass cn/... ct/... r/.. l/...") +activate LogicManager + +LogicManager -> TimesTableParser : parseCommand("addclass cn/... ct/... r/.. l/...") +activate TimesTableParser + +create AddClassCommandParser +TimesTableParser -> AddClassCommandParser +activate AddClassCommandParser + +AddClassCommandParser --> TimesTableParser +deactivate AddClassCommandParser + +TimesTableParser -> AddClassCommandParser : parse("cn/... ct/... r/.. l/...") +activate AddClassCommandParser + +ref over AddClassCommandParser : generate StudentNameList and TuitionClass + +create AddClassCommand +AddClassCommandParser -> AddClassCommand : AddClassCommand(t) +activate AddClassCommand + +AddClassCommand --> AddClassCommandParser : a +deactivate AddClassCommand + +AddClassCommandParser --> TimesTableParser : a +deactivate AddClassCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddClassCommandParser -[hidden]-> TimesTableParser +destroy AddClassCommandParser + +TimesTableParser --> LogicManager : a +deactivate TimesTableParser + +LogicManager -> AddClassCommand : execute() +activate AddClassCommand + +AddClassCommand -> Model : addTuitionClass(t) +activate Model +deactivate Model + + + +create CommandResult +AddClassCommand -> CommandResult +activate CommandResult + +CommandResult --> AddClassCommand +deactivate CommandResult + +AddClassCommand --> LogicManager : result +deactivate AddClassCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/AddToClassCreationSequenceDiagram.puml b/docs/diagrams/AddToClassCreationSequenceDiagram.puml new file mode 100644 index 00000000000..89c2d021b4e --- /dev/null +++ b/docs/diagrams/AddToClassCreationSequenceDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include Style.puml + +box Logic LOGIC_COLOR_T1 + +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":AddToClassCommandParser" as AddToClassCommandParser LOGIC_COLOR +participant "c:AddToClassCommand" as AddToClassCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +-> LogicManager: execute("addtoclass 1 1") +activate LogicManager +LogicManager -> TimesTableParser: parseCommand("addtoclass 1 1") +activate TimesTableParser +create AddToClassCommandParser +TimesTableParser -> AddToClassCommandParser +activate AddToClassCommandParser +TimesTableParser <-- AddToClassCommandParser +deactivate AddToClassCommandParser +TimesTableParser -> AddToClassCommandParser: parse("1 1") +activate AddToClassCommandParser +create AddToClassCommand +AddToClassCommandParser -> AddToClassCommand: AddToClassCommand(indexArray) +activate AddToClassCommand +AddToClassCommandParser <-- AddToClassCommand: c +deactivate AddToClassCommand +TimesTableParser <-- AddToClassCommandParser: c +deactivate AddToClassCommandParser +LogicManager <-- TimesTableParser: c +deactivate TimesTableParser + +LogicManager -> AddToClassCommand: execute() +activate AddToClassCommand +ref over AddToClassCommand, Model : Match Student and Class Indices to their objects. Create updated Name list. +AddToClassCommand -> Model: setClass(classToAddTo, editedClass) +activate Model +AddToClassCommand <-- Model +deactivate Model +create CommandResult +AddToClassCommand -> CommandResult +activate CommandResult +AddToClassCommand <-- CommandResult +deactivate CommandResult +LogicManager <-- AddToClassCommand: result +deactivate AddToClassCommand +<-- LogicManager: result +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/AddToClassExecutionSequenceDiagram.puml b/docs/diagrams/AddToClassExecutionSequenceDiagram.puml new file mode 100644 index 00000000000..b38adbaa8e4 --- /dev/null +++ b/docs/diagrams/AddToClassExecutionSequenceDiagram.puml @@ -0,0 +1,89 @@ +@startuml +!include Style.puml + +box Logic LOGIC_COLOR_T1 +participant ":AddToClassCommand" as AddToClassCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "classList:ObservableList" as tuitionClassList MODEL_COLOR +participant "c1:TuitionClass" as TuitionClass MODEL_COLOR +participant "studentList:ObservableList" as studentList MODEL_COLOR +participant "c2:TuitionClass" as newTuitionClass MODEL_COLOR +participant "sn:StudentNameList" as newStudentNameList MODEL_COLOR +end box + +[-> AddToClassCommand: execute() +activate AddToClassCommand + +AddToClassCommand -> Model: getFilteredStudentList() +activate Model +Model -> studentList: Get student list +activate studentList +Model <-- studentList: studentList +deactivate +AddToClassCommand <-- Model: studentList +deactivate Model + +AddToClassCommand -> Model: getFilteredTuitionClassList() +activate Model +Model -> tuitionClassList: Get class list +activate tuitionClassList +Model <-- tuitionClassList: classList +deactivate +AddToClassCommand <-- Model: classList +deactivate Model + +AddToClassCommand -> AddToClassCommand ++: checkIndicesAreValid(studentList, classList) +AddToClassCommand --> AddToClassCommand -- + +AddToClassCommand -> AddToClassCommand ++: createNameList(studentIndices, studentList) +AddToClassCommand --> AddToClassCommand --: namesToAdd + +AddToClassCommand -> tuitionClassList: get(classIndex) +activate tuitionClassList +tuitionClassList -> TuitionClass: Get tuition class +activate TuitionClass +tuitionClassList <-- TuitionClass: c1 +deactivate +AddToClassCommand <-- tuitionClassList: c1 +deactivate tuitionClassList + +AddToClassCommand -> TuitionClass: getStudentList() +activate TuitionClass +AddToClassCommand <-- TuitionClass: nameList +deactivate + +create newStudentNameList +AddToClassCommand -> newStudentNameList : StudentNameList() +activate newStudentNameList +newStudentNameList --> AddToClassCommand +deactivate newStudentNameList +AddToClassCommand -> newStudentNameList : addAll(nameList) +activate newStudentNameList +newStudentNameList --> AddToClassCommand : updatedNameList +deactivate newStudentNameList +AddToClassCommand -> newStudentNameList : addAll(namesToAdd) +activate newStudentNameList +newStudentNameList --> AddToClassCommand : updatedNameList +deactivate newStudentNameList + +create newTuitionClass +AddToClassCommand -> newTuitionClass ++ :Create new tuition class with updatedNameList +AddToClassCommand <-- newTuitionClass -- : c2 + + +AddToClassCommand -> Model: setClass(c1, c2) +activate Model +AddToClassCommand <-- Model +deactivate Model +create CommandResult +AddToClassCommand -> CommandResult +activate CommandResult +AddToClassCommand <-- CommandResult +deactivate CommandResult +[<-- AddToClassCommand: result +deactivate AddToClassCommand + +@enduml diff --git a/docs/diagrams/AddToClassRef1.puml b/docs/diagrams/AddToClassRef1.puml new file mode 100644 index 00000000000..2c1960b1724 --- /dev/null +++ b/docs/diagrams/AddToClassRef1.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml + +group sd generate updated StudentNameList + +box Logic LOGIC_COLOR_T1 +participant ":AddToClass" as AddToClass LOGIC_COLOR +participant ":StudentNameList" as updatedNameList LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "lastShownTuitionClassList:FilteredList" as FilteredClass MODEL_COLOR +participant "classToAddTo:TuitionClass" as classToAddTo MODEL_COLOR +end box + +activate AddToClass + +AddToClass -> AddToClass : createNameList(...) +activate AddToClass +return namesToAdd + +AddToClass -> FilteredClass : get(...) +activate FilteredClass +return classToAddTo + +AddToClass -> classToAddTo : getStudentList() +activate classToAddTo +return currentStudentNameList + + +create updatedNameList +AddToClass -> updatedNameList : StudentNameList() +activate updatedNameList +return + +AddToClass -> updatedNameList : addAll(currentStudentNameList) +activate updatedNameList +return + +AddToClass -> updatedNameList : addAll(namesToAdd) +activate updatedNameList +return + +end +@enduml diff --git a/docs/diagrams/AddToClassRef2.puml b/docs/diagrams/AddToClassRef2.puml new file mode 100644 index 00000000000..9338d27c23a --- /dev/null +++ b/docs/diagrams/AddToClassRef2.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml + +group sd generate updated TuitionClass + +box Logic LOGIC_COLOR_T1 +participant ":AddToClass" as AddToClass LOGIC_COLOR +participant ":EditClassDescriptor" as descriptor LOGIC_COLOR +participant ":EditClassCommand" as EditClassCommand <> LOGIC_COLOR +end box + +create descriptor +AddToClass -> descriptor : EditClassDescriptor() +activate descriptor + +return + + +AddToClass -> descriptor : setStudentList(updatedStudentNameList) +activate descriptor +return + +AddToClass -> EditClassCommand : createEditedClass(classToAddTo, editClassDescriptor) +activate EditClassCommand +return editedClass + +end +@enduml diff --git a/docs/diagrams/AddToClassSequenceDiagram.puml b/docs/diagrams/AddToClassSequenceDiagram.puml new file mode 100644 index 00000000000..e7774e6ab78 --- /dev/null +++ b/docs/diagrams/AddToClassSequenceDiagram.puml @@ -0,0 +1,42 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddToClass" as AddToClass LOGIC_COLOR +'participant ":EditClassDescriptor" as descripter LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +'participant "lastShownTuitionClassList:FilteredList" as FilteredClass MODEL_COLOR +end box + +[-> LogicManager : execute("addtoclass 1 1") +activate LogicManager + +LogicManager -> AddToClass : execute() +activate AddToClass + +AddToClass -> ModelManager : getFilteredStudentList() +AddToClass <-- ModelManager +AddToClass -> ModelManager : getFilteredClassList() +AddToClass <-- ModelManager +AddToClass -> AddToClass : checkIndicesAreInRange() +activate AddToClass +AddToClass --> AddToClass +deactivate + +ref over AddToClass, ModelManager : generate updated StudentNameList + +ref over AddToClass, ModelManager : generate updated TuitionClass + +AddToClass -> ModelManager : setClass(classToAdd, editedClass) +AddToClass <-- ModelManager + +return + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..eb4062823bc 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -13,13 +13,13 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteStudent(s) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveTimesTable(timesTable) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BuildTimetableDayActivityDiagram.puml b/docs/diagrams/BuildTimetableDayActivityDiagram.puml new file mode 100644 index 00000000000..2a8a6562aea --- /dev/null +++ b/docs/diagrams/BuildTimetableDayActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +title: Activity: Build TimetableDay +start +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Mon"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Tue"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Wed"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Thu"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Fri"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Sat"; +:Increase row index by 1; +:Build TimetableDay at\nthe left (column index 0)\nlabelled "Sun"; + +stop +@enduml diff --git a/docs/diagrams/BuildTimetableHeaderActivityDiagram.puml b/docs/diagrams/BuildTimetableHeaderActivityDiagram.puml new file mode 100644 index 00000000000..70483239510 --- /dev/null +++ b/docs/diagrams/BuildTimetableHeaderActivityDiagram.puml @@ -0,0 +1,22 @@ +@startuml +title: Activity: Build TimetableHeader +start +:Build TimetableHeaderLabel at\ntop left (row index = 0 and\ncolumn index = 0) labelled "Time Slots"; + +:Find earliest start hour and latest end hour ****; + +while () is ([earliest start hour\n< latest end hour]) + if () then ([earliest start hour == 23:30]) + :Add new TimetableHeaderTiming\nfrom earliest start hour to \nearliest start hour + 29 mins; + :Break out of while loop,\ngo to else portion of the loop; + else ([else]) + :Add new TimetableHeaderTiming\nfrom earliest start hour to \nearliest start hour + 30 mins; + :Increase earliest start hour by 30mins; + endif +endwhile ([else]) +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + + +stop +@enduml diff --git a/docs/diagrams/BuildTimetableOverallDiagram.puml b/docs/diagrams/BuildTimetableOverallDiagram.puml new file mode 100644 index 00000000000..e5cafa8dd16 --- /dev/null +++ b/docs/diagrams/BuildTimetableOverallDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start + +:Clear Displayed Timetable; + +:Build TimetableHeader ****; + +:Build TimetableDay ****; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([Number of TuitionClasses > 0]) + :Build TimetableTuitionClassSlots ****; +else ([else]) +endif +stop +@enduml diff --git a/docs/diagrams/BuildTimetableTuitionClassSlotsActivityDiagram.puml b/docs/diagrams/BuildTimetableTuitionClassSlotsActivityDiagram.puml new file mode 100644 index 00000000000..fab50ac803f --- /dev/null +++ b/docs/diagrams/BuildTimetableTuitionClassSlotsActivityDiagram.puml @@ -0,0 +1,34 @@ +@startuml +title: Activity: Build TimetableTuitionClassSlots +start +:Sort tuitionClasses based on their class timings\nstarting from the earliest; +:Find earliest start hour and latest end hour ****; +:Store the earliest start hour in a variable called\n'previousTime' which will act as a pointer to the end\nof the latest TuitionClassSlot we built up to; +:int i = 0;; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. +while () is ([i < TuitionClasses.size()]) +:Get tuitionClass at index i; + if () then ([tuitionClass at index i's\nstart time != previousTime]) + :Add empty slot from\npreviousTime to the\nstart time of the tuitionClass; + + + else ([else]) + endif + + :Add TimetableTuitionClassSlot from start\ntime to end time of tuitionClass at index i; + if () then ([tuitionClass at index i + 1\nis not on the same day as tuitionClass\nat index i]) + :previousTime = Earliest start hour; + + else ([else]) + :previousTime = end time of tuitionClass at index i; + endif +:i++;; + +endwhile ([else]) + + + + +stop +@enduml diff --git a/docs/diagrams/ClassPanelDiagram.puml b/docs/diagrams/ClassPanelDiagram.puml new file mode 100644 index 00000000000..bbcf9c725b8 --- /dev/null +++ b/docs/diagrams/ClassPanelDiagram.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package ClassesUi <>{ +Class ClassPanel +Class StudentClassPanel +Class StudentClassTabCard +Class TuitionClassPanel +Class TuitionClassCard +} + +package Model <> { +Class Student +Class TuitionClass +} + +Class "{abstract}\nUiPart" as UiPart + +Class MainWindow +MainWindow *--> "1" ClassPanel +ClassPanel -down-> "1" StudentClassPanel +ClassPanel -down-> "1" TuitionClassPanel +StudentClassPanel -down-> "*" StudentClassTabCard +TuitionClassPanel -down-> "*" TuitionClassCard +StudentClassTabCard ..> Student +TuitionClassCard ..> TuitionClass + +ClassPanel --|> UiPart +StudentClassPanel --|> UiPart +TuitionClassPanel --|> UiPart +StudentClassTabCard --|> UiPart +TuitionClassCard --|> UiPart + +@enduml diff --git a/docs/diagrams/ClassesUiSequenceDiagram.puml b/docs/diagrams/ClassesUiSequenceDiagram.puml new file mode 100644 index 00000000000..db0faf4fc74 --- /dev/null +++ b/docs/diagrams/ClassesUiSequenceDiagram.puml @@ -0,0 +1,105 @@ +@startuml +!include style.puml + +skinparam ParticipantPadding -2 +box MainWindow UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +end box + +box ClassesUi UI_COLOR_T1 +participant ":ClassPanel" as ClassPanel UI_COLOR_T3 +participant ":Student\nClassPanel" as StudentClassPanel UI_COLOR_T3 +participant ":Tuition\nClassPanel" as TuitionClassPanel UI_COLOR_T3 +participant ":StudentList\nClassTabViewCell" as StudentListClassTabViewCell UI_COLOR_T3 +participant ":Student\nClassTabCard" as StudentClassTabCard UI_COLOR_T3 +participant ":Tuition\nClassListViewCell" as TuitionClassListViewCell UI_COLOR_T3 +participant ":Tuition\nClassCard" as TuitionClassCard UI_COLOR_T3 +participant ":Filtered\nStudentList" as FilteredStudentList UI_COLOR_T3 +end box + +[-> MainWindow : fillInnerParts() +activate MainWindow + +create FilteredStudentList + +create ClassPanel +MainWindow -> ClassPanel : new ClassPanel\n(List,\nList) +activate ClassPanel + +create StudentClassPanel +ClassPanel -> StudentClassPanel : new StudentClassPanel\n(studentList) +activate StudentClassPanel + +loop for every student +create StudentListClassTabViewCell +StudentClassPanel -> StudentListClassTabViewCell +activate StudentListClassTabViewCell + +create StudentClassTabCard +StudentListClassTabViewCell -> StudentClassTabCard +activate StudentClassTabCard +StudentClassTabCard --> StudentListClassTabViewCell +deactivate StudentClassTabCard +StudentListClassTabViewCell --> StudentClassPanel + +deactivate StudentListClassTabViewCell + +end + +StudentClassPanel --> ClassPanel +deactivate StudentClassPanel + +create TuitionClassPanel +ClassPanel -> TuitionClassPanel : new TuitionClassPanel\n(studentList, tuitionClassList) +activate TuitionClassPanel + +TuitionClassPanel --> ClassPanel + +deactivate TuitionClassPanel + + +ClassPanel -> TuitionClassPanel : setStudentClassList(...) +activate TuitionClassPanel + + +loop for every class + +create TuitionClassListViewCell +TuitionClassPanel -> TuitionClassListViewCell +activate TuitionClassListViewCell + + +create TuitionClassCard +TuitionClassListViewCell -> TuitionClassCard +activate TuitionClassCard + +opt when clicked +TuitionClassCard -> TuitionClassCard : onMouseClick() +activate TuitionClassCard +TuitionClassCard -> TuitionClassCard : selectTuitionClass() +activate TuitionClassCard +TuitionClassCard -> FilteredStudentList : filtered(student -> ...) +activate FilteredStudentList +FilteredStudentList -> TuitionClassCard +deactivate FilteredStudentList + + +deactivate TuitionClassCard +deactivate TuitionClassCard +end + + +TuitionClassCard --> TuitionClassListViewCell +deactivate TuitionClassCard +TuitionClassListViewCell --> TuitionClassPanel +deactivate TuitionClassListViewCell +end +TuitionClassPanel --> ClassPanel +deactivate TuitionClassPanel +ClassPanel --> MainWindow +deactivate ClassPanel + +[<--MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/CommandObserverPattern.puml b/docs/diagrams/CommandObserverPattern.puml new file mode 100644 index 00000000000..972bd2a0da2 --- /dev/null +++ b/docs/diagrams/CommandObserverPattern.puml @@ -0,0 +1,26 @@ + @startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Interface Ui <> +Interface CommandObserver <> +Class UiManager +Class MainWindow + +} + +package Logic <> { +Class "{abstract}\nCommand" as Command LOGIC_COLOR +Class XYZCommand LOGIC_COLOR +} + +UiManager .right..|> Ui +UiManager -down-> "1" MainWindow +MainWindow .right..|> CommandObserver +XYZCommand -up-|> Command LOGIC_COLOR_T4 + +Command --> CommandObserver LOGIC_COLOR_T4 : < Observes +@enduml diff --git a/docs/diagrams/DeleteClassRef1.puml b/docs/diagrams/DeleteClassRef1.puml new file mode 100644 index 00000000000..d2a68d5e98b --- /dev/null +++ b/docs/diagrams/DeleteClassRef1.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml + +group sd get TuitionClass to delete and delete from model + +box Logic LOGIC_COLOR_T1 +participant "d:DeleteClassCommand" as DeleteClassCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":TimesTable" as TimesTable MODEL_COLOR +participant ":UniqueClassList" as UniqueClassList MODEL_COLOR +participant "internalList:ObservableList" as internalList MODEL_COLOR +end box + +activate DeleteClassCommand + +DeleteClassCommand -> Model : getFilteredTuitionClassList() +activate Model + + + +Model --> DeleteClassCommand : classList +deactivate Model + +DeleteClassCommand -> DeleteClassCommand : classList.get(index) +activate DeleteClassCommand +DeleteClassCommand --> DeleteClassCommand : tuitionclass +deactivate DeleteClassCommand + +DeleteClassCommand -> Model : deleteTuitionClass(tuitionclass) +activate Model +Model -> TimesTable : deleteTuitionClass(tuitionclass) +activate TimesTable +TimesTable -> UniqueClassList : delete(tuitionclass) +activate UniqueClassList +UniqueClassList -> internalList : remove(tuitionclass) +activate internalList +deactivate internalList +deactivate UniqueClassList +deactivate TimesTable +deactivate Model + +end +@enduml diff --git a/docs/diagrams/DeleteClassSequenceDiagram.puml b/docs/diagrams/DeleteClassSequenceDiagram.puml new file mode 100644 index 00000000000..7b068c32e47 --- /dev/null +++ b/docs/diagrams/DeleteClassSequenceDiagram.puml @@ -0,0 +1,67 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":DeleteClassCommandParser" as DeleteClassCommandParser LOGIC_COLOR +participant "d:DeleteClassCommand" as DeleteClassCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("deleteclass 1") +activate LogicManager + +LogicManager -> TimesTableParser : parseCommand("deleteclass 1") +activate TimesTableParser + +create DeleteClassCommandParser +TimesTableParser -> DeleteClassCommandParser +activate DeleteClassCommandParser + +DeleteClassCommandParser --> TimesTableParser +deactivate DeleteClassCommandParser + +TimesTableParser -> DeleteClassCommandParser : parse("1") +activate DeleteClassCommandParser + +create DeleteClassCommand +DeleteClassCommandParser -> DeleteClassCommand +activate DeleteClassCommand + +DeleteClassCommand --> DeleteClassCommandParser : d +deactivate DeleteClassCommand + +DeleteClassCommandParser --> TimesTableParser : d +deactivate DeleteClassCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteClassCommandParser -[hidden]-> TimesTableParser +destroy DeleteClassCommandParser + +TimesTableParser --> LogicManager : d +deactivate TimesTableParser + +LogicManager -> DeleteClassCommand : execute() +activate DeleteClassCommand + +ref over DeleteClassCommand, Model : get TuitionClass to delete and delete from model + + + +create CommandResult +DeleteClassCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteClassCommand +deactivate CommandResult + +DeleteClassCommand --> LogicManager : result +deactivate DeleteClassCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..5149acd09c9 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,7 +3,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR @@ -16,17 +16,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> TimesTableParser : parseCommand("delete 1") +activate TimesTableParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +TimesTableParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> TimesTableParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +TimesTableParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -36,19 +36,19 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> TimesTableParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> TimesTableParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +TimesTableParser --> LogicManager : d +deactivate TimesTableParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deleteStudent(1) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/FindEarliestAndLatestHourActivityDiagram.puml b/docs/diagrams/FindEarliestAndLatestHourActivityDiagram.puml new file mode 100644 index 00000000000..98cea015ed8 --- /dev/null +++ b/docs/diagrams/FindEarliestAndLatestHourActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +title: Activity: Find earliest start hour and latest end hour +start +if () then ([Number of TuitionClasses > 0]) + :Iterate through the list of tuition\nclasses and find the earliest class\nstart timing and round down to\nthe nearest hour; + :Set earliest start hour to the earliest class\nstart timing found in the previous step or\nthe default earliest start timing(09:00),\nwhichever is earlier; + :Iterate through the list of tuition\nclasses and find the latest class\nend timing and round up to the\nnearest hour; + :Set latest end hour to the latest class\nend timing found in the previous step\nor the default latest end timing(18:00),\nwhichever is later; + + +else ([else]) + :Set earliest start hour\nto default earliest start timing\n(09:00); + :Set latest end hour\nto default latest end timing\n(18:00); + +endif +stop +@enduml diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml new file mode 100644 index 00000000000..cd84efd403b --- /dev/null +++ b/docs/diagrams/FindSequenceDiagram.puml @@ -0,0 +1,33 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":FindTagCommand" as FindTagCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +participant "filteredStudent:FilteredList" as FilteredStudents MODEL_COLOR +end box + +[-> LogicManager : execute("findtag maths") +activate LogicManager + +LogicManager -> FindTagCommand : execute() +activate FindTagCommand + +FindTagCommand -> ModelManager : updateFilteredStudentList(predicate) +activate ModelManager + +ModelManager -> FilteredStudents : setPredicate(predicate) +activate FilteredStudents + +return +return +return + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 6d14b17b361..2550deee8c2 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR package Logic { -Class AddressBookParser +Class TimesTableParser Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command @@ -23,23 +23,32 @@ Class HiddenModel #FFFFFF package Storage{ } +package UI { +Class HiddenUI #FFFFFF +} + Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right->"1" TimesTableParser +TimesTableParser ..> XYZCommand : creates > XYZCommand -up-|> Command LogicManager .left.> Command : executes > -LogicManager --> Model + +LogicManager -right-> Model LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage -Command .right.> Model +Command .up.> Model +Command -right-> UI : Observes < +XYZCommand .down.> UI note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +UI --> Logic +LogicManager --> UI Logic ..> CommandResult LogicManager .down.> CommandResult Command .up.> CommandResult : produces > diff --git a/docs/diagrams/MainWindowDiagram.puml b/docs/diagrams/MainWindowDiagram.puml new file mode 100644 index 00000000000..5685fd4dcb7 --- /dev/null +++ b/docs/diagrams/MainWindowDiagram.puml @@ -0,0 +1,74 @@ + @startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + + +'IGNORE THIS FILE FIRST +package MainWindow <>{ +Class "{abstract}\nUiPart" as UiPart +Class MainWindow +Class HelpWindow +Class ResultDisplay +Class PersonListPanel +Class PersonCard +Class StatusBarFooter +Class CommandBox + +package Timetable <> { +Class HiddenTimetable #FFFFFF +} + +} + + +Class UiManager +Interface Ui <> +Interface CommandObserver <> + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF + +UiManager .right.|> Ui +UiManager -left-> "1" MainWindow + +MainWindow .up.|> CommandObserver +MainWindow *-up---> "\n\n\n1" Timetable +MainWindow *-left-> "1" CommandBox +MainWindow *-down-> "1" ResultDisplay +MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" StatusBarFooter +MainWindow --> "0..1" HelpWindow +MainWindow -down-|> UiPart + +PersonListPanel -down-> "*" PersonCard + +ResultDisplay --|> UiPart +CommandBox --|> UiPart +PersonListPanel --|> UiPart +PersonCard --|> UiPart +StatusBarFooter --|> UiPart +HelpWindow --|> UiPart +Timetable -right-|> UiPart + +PersonCard ..> Model +UiManager -up-> Logic +MainWindow -right-> Logic + +Logic -up-> "*" CommandObserver + +PersonListPanel -[hidden]left- HelpWindow +HelpWindow -[hidden]left- CommandBox +CommandBox -[hidden]left- ResultDisplay +ResultDisplay -[hidden]left- StatusBarFooter + +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 1122257bd9a..ebf8a299be2 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,50 +5,48 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Interface ReadOnlyAddressBook <> +Interface ReadOnlyTimestable <> Interface ReadOnlyUserPrefs <> Interface Model <> -Class AddressBook -Class ReadOnlyAddressBook +Class Timestable +Class ReadOnlyTimestable Class Model Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs +Class UniqueStudentList +Class UniqueClassList + +package TuitionClass <> { +} +package Student <> { +} + -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag } + + Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +Timestable .up.|> ReadOnlyTimestable ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyTimestable +ModelManager -left-> "1" Timestable ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +Timestable *--> "1" UniqueStudentList +UniqueStudentList --> "~* all" Student -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Timestable *--> "1" UniqueClassList +UniqueClassList -right-> "~* all" TuitionClass -ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Student +ModelManager -->"~* filtered" TuitionClass @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 6ba585cba01..417c687fb31 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Interface Parser <> -Class AddressBookParser +Class TimesTableParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> TimesTableParser -AddressBookParser .down.> XYZCommandParser: creates > +TimesTableParser .down.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > +TimesTableParser ..> Command : returns > XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/RemoveFromClassCommandSequenceDiagram.puml b/docs/diagrams/RemoveFromClassCommandSequenceDiagram.puml new file mode 100644 index 00000000000..0ac0b9fbba3 --- /dev/null +++ b/docs/diagrams/RemoveFromClassCommandSequenceDiagram.puml @@ -0,0 +1,68 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!include Style.puml + + + +box Logic LOGIC_COLOR_T1 +participant "c:RemoveFromClassCommand" as RemoveFromClassCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "classList:ObservableList" as tuitionClassList MODEL_COLOR +participant "classToRemoveFrom:TuitionClass" as TuitionClass MODEL_COLOR +participant "studentList:ObservableList" as studentList MODEL_COLOR +participant "editedClass:TuitionClass" as newTuitionClass MODEL_COLOR +end box + +activate RemoveFromClassCommand + +group sd Match Student and Class Indices to their objects. Create updated Name list. +RemoveFromClassCommand -> Model: getFilteredTuitionClassList() +activate Model +Model -> tuitionClassList: Get class list +activate tuitionClassList +Model <-- tuitionClassList: classList +deactivate +RemoveFromClassCommand <-- Model: classList +deactivate Model + +RemoveFromClassCommand -> tuitionClassList: get(classIndex) +activate tuitionClassList +tuitionClassList -> TuitionClass: Get tuition class +activate TuitionClass +tuitionClassList <-- TuitionClass: classToRemoveFrom +deactivate +RemoveFromClassCommand <-- tuitionClassList: classToRemoveFrom +deactivate tuitionClassList + +RemoveFromClassCommand -> TuitionClass: getStudentList() +activate TuitionClass +RemoveFromClassCommand <-- TuitionClass: nameList +deactivate + +RemoveFromClassCommand -> Model: getFilteredStudentList() +activate Model +Model -> studentList: Get student list +activate studentList +Model <-- studentList: studentList +deactivate +RemoveFromClassCommand <-- Model: studentList +deactivate Model + +RemoveFromClassCommand -> RemoveFromClassCommand ++: checkIndicesAreValid(studentIndices, studentList) +RemoveFromClassCommand --> RemoveFromClassCommand -- + +RemoveFromClassCommand -> RemoveFromClassCommand ++: createNewNameList(studentIndices, studentList) +RemoveFromClassCommand --> RemoveFromClassCommand --: newNameList + +create newTuitionClass +RemoveFromClassCommand -> newTuitionClass ++ :Create new tuition class with newNameList + + +RemoveFromClassCommand <-- newTuitionClass -- : editedClass + +end +@enduml diff --git a/docs/diagrams/RemoveFromClassOverviewDiagram.puml b/docs/diagrams/RemoveFromClassOverviewDiagram.puml new file mode 100644 index 00000000000..dc32966a891 --- /dev/null +++ b/docs/diagrams/RemoveFromClassOverviewDiagram.puml @@ -0,0 +1,59 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!include Style.puml + +box Logic LOGIC_COLOR_T1 + +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":RemoveFromClassCommandParser" as RemoveFromClassCommandParser LOGIC_COLOR +participant "c:RemoveFromClassCommand" as RemoveFromClassCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +-> LogicManager: execute("removefromclass 1 1") +activate LogicManager +LogicManager -> TimesTableParser: parseCommand("removefromclass 1 1") +activate TimesTableParser +create RemoveFromClassCommandParser +TimesTableParser -> RemoveFromClassCommandParser +activate RemoveFromClassCommandParser +TimesTableParser <-- RemoveFromClassCommandParser +deactivate RemoveFromClassCommandParser +TimesTableParser -> RemoveFromClassCommandParser: parse("1 1") +activate RemoveFromClassCommandParser +create RemoveFromClassCommand +RemoveFromClassCommandParser -> RemoveFromClassCommand: RemoveFromClassCommand(indexArray) +activate RemoveFromClassCommand +RemoveFromClassCommandParser <-- RemoveFromClassCommand: c +deactivate RemoveFromClassCommand +TimesTableParser <-- RemoveFromClassCommandParser: c +deactivate RemoveFromClassCommandParser +RemoveFromClassCommandParser -[hidden]-> TimesTableParser +destroy RemoveFromClassCommandParser +LogicManager <-- TimesTableParser: c +deactivate TimesTableParser + +LogicManager -> RemoveFromClassCommand: execute() +activate RemoveFromClassCommand +ref over RemoveFromClassCommand, Model : Match Student and Class Indices to their objects. Create updated Name list. +RemoveFromClassCommand -> Model: setClass(classToRemoveFrom, editedClass) +activate Model +RemoveFromClassCommand <-- Model +deactivate Model +create CommandResult +RemoveFromClassCommand -> CommandResult +activate CommandResult +RemoveFromClassCommand <-- CommandResult +deactivate CommandResult +LogicManager <-- RemoveFromClassCommand: result +deactivate RemoveFromClassCommand +<-- LogicManager: result +deactivate LogicManager + + +@enduml diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml new file mode 100644 index 00000000000..f401d47f8df --- /dev/null +++ b/docs/diagrams/SortSequenceDiagram.puml @@ -0,0 +1,85 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":SortCommandParser" as SortCommandParser LOGIC_COLOR +participant "s:SortCommand" as SortCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +box UI UI_COLOR_T1 +participant "mainWindow:CommandObserver" as CommandObserver UI_COLOR +end box + +[-> LogicManager : execute("sort name asc") +activate LogicManager + +LogicManager -> TimesTableParser : parseCommand("sort name asc") +activate TimesTableParser + +create SortCommandParser +TimesTableParser -> SortCommandParser +activate SortCommandParser + +SortCommandParser --> TimesTableParser +deactivate SortCommandParser + +TimesTableParser -> SortCommandParser : parse("name asc") +activate SortCommandParser + +create SortCommand +SortCommandParser -> SortCommand +activate SortCommand + +SortCommand --> SortCommandParser : s +deactivate SortCommand + +SortCommandParser --> TimesTableParser : s +deactivate SortCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +SortCommandParser -[hidden]-> TimesTableParser +destroy SortCommandParser + +TimesTableParser --> LogicManager : s +deactivate TimesTableParser + +LogicManager -> SortCommand : execute() +activate SortCommand + +SortCommand -> Model : sortStudents() +activate Model + +Model --> SortCommand +deactivate Model + +SortCommand -> CommandObserver : updateView(STUDENTS) +activate CommandObserver + +CommandObserver --> SortCommand +deactivate CommandObserver + +create CommandResult +SortCommand -> CommandResult +activate CommandResult + +CommandResult --> SortCommand +deactivate CommandResult +'Hidden arrow to position the destroy marker below the end of the activation bar. +'CommandResult -[hidden]-> TimesTableParser +'destroy CommandResult + +SortCommand --> LogicManager : result +deactivate SortCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +'SortCommandParser -[hidden]-> TimesTableParser +'destroy SortCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StorageClassDiagramWTuitionClass.puml b/docs/diagrams/StorageClassDiagramWTuitionClass.puml new file mode 100644 index 00000000000..6e10bab1080 --- /dev/null +++ b/docs/diagrams/StorageClassDiagramWTuitionClass.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor STORAGE_COLOR +skinparam classBackgroundColor STORAGE_COLOR + +package Storage{ + +package "UserPrefs Storage" #F4F6F6{ +Interface UserPrefsStorage <> +Class JsonUserPrefsStorage +} + +Interface Storage <> +Class StorageManager + +package "TimesTable Storage" #F4F6F6{ +Interface TimesTableStorage <> +Class JsonTimesTableStorage +Class JsonSerializableTimesTable +Class JsonAdaptedStudent +Class JsonAdaptedTag +Class JsonAdaptedTuitionClass +Class JsonAdaptedNok +} + +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Storage + +StorageManager .up.|> Storage +StorageManager -up-> "1" UserPrefsStorage +StorageManager -up-> "1" TimesTableStorage + +Storage -left-|> UserPrefsStorage +Storage -right-|> TimesTableStorage + +JsonUserPrefsStorage .up.|> UserPrefsStorage +JsonTimesTableStorage .up.|> TimesTableStorage +JsonTimesTableStorage ..> JsonSerializableTimesTable +JsonSerializableTimesTable --> "*" JsonAdaptedStudent +JsonSerializableTimesTable --> "*" JsonAdaptedTuitionClass +JsonAdaptedStudent --> "*" JsonAdaptedTag +JsonAdaptedStudent --> "1" JsonAdaptedNok +@enduml diff --git a/docs/diagrams/StudentModelClassDiagram.puml b/docs/diagrams/StudentModelClassDiagram.puml new file mode 100644 index 00000000000..36f3e51ab54 --- /dev/null +++ b/docs/diagrams/StudentModelClassDiagram.puml @@ -0,0 +1,40 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Student <>{ + +Class Person +Class Student +Class Address +Class Email +Class Name +Class Phone +Class Tag +Class NOK + +} + +Class HiddenOutside #FFFFFF +HiddenOutside ...> " * all"Student +HiddenOutside ...> " * filtered"Student + +Person <|-up- Student +Person <|-right- NOK + +Person *--> "1" Name +Person *--> "1" Phone +Person *--> "1" Email +Person *--> "1" Address +Student *-right-> "*" Tag +Student *--> "1" NOK + + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Address +Address -[hidden]right-> Email + + +@enduml diff --git a/docs/diagrams/StudentsDiagram.puml b/docs/diagrams/StudentsDiagram.puml new file mode 100644 index 00000000000..a804b08318f --- /dev/null +++ b/docs/diagrams/StudentsDiagram.puml @@ -0,0 +1,27 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package StudentsUi <>{ +Class StudentListPanel +Class StudentCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class "{abstract}\nUiPart" as UiPart + +Class MainWindow +MainWindow *--> "1" StudentListPanel +StudentListPanel --> "*" StudentCard +StudentListPanel --|> UiPart +StudentCard .right.> Model +StudentCard --|> UiPart + + + +@enduml diff --git a/docs/diagrams/TimetableDiagram.puml b/docs/diagrams/TimetableDiagram.puml new file mode 100644 index 00000000000..6f7971a27a6 --- /dev/null +++ b/docs/diagrams/TimetableDiagram.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package TimetableUi <>{ +Class TimetablePanel +Class TimetableDay +Class TimetableEmptySlot +Class TimetableHeader +Class TimetableRegion +Class TimetableTuitionClassSlot +Class TimetableHeaderTiming +Class TimetableHeaderLabel +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class "{abstract}\nUiPart" as UiPart + +Class MainWindow +MainWindow *--> "1" TimetablePanel +TimetablePanel *--> "7" TimetableDay +TimetablePanel *--> "*" TimetableEmptySlot +TimetablePanel *--> "18...48" TimetableHeaderTiming +TimetablePanel *--> "1" TimetableHeaderLabel +TimetablePanel *--> "*" TimetableTuitionClassSlot + +TimetableHeaderLabel -down-|> TimetableHeader +TimetableHeaderTiming -down-|> TimetableHeader + +TimetableDay --|> TimetableRegion +TimetableEmptySlot --|> TimetableRegion +TimetableHeader --|> TimetableRegion +TimetableTuitionClassSlot --|> TimetableRegion + +TimetableRegion --|> UiPart +TimetablePanel --|> UiPart + +TimetablePanel .right.> Model + +@enduml diff --git a/docs/diagrams/TimetableUiDaySequenceDiagram.puml b/docs/diagrams/TimetableUiDaySequenceDiagram.puml new file mode 100644 index 00000000000..37ea54accc3 --- /dev/null +++ b/docs/diagrams/TimetableUiDaySequenceDiagram.puml @@ -0,0 +1,33 @@ +@startuml +!include style.puml + + +box TimetableUi UI_COLOR_T1 +participant ":TimetablePanel" as TimetablePanel UI_COLOR_T3 +participant ":TimetableDay" as TimetableDay UI_COLOR_T3 +end box + + +create TimetablePanel +[-[hidden]-> TimetablePanel +activate TimetablePanel + +group sd buildDays +TimetablePanel -> TimetablePanel : buildDays() +activate TimetablePanel + +loop 7 times (Mon to Sun) +create TimetableDay +TimetablePanel -> TimetableDay +activate TimetableDay +TimetableDay --> TimetablePanel +deactivate TimetableDay +end + + +TimetablePanel --> TimetablePanel +deactivate TimetablePanel +end + + +@enduml diff --git a/docs/diagrams/TimetableUiHeaderSequenceDiagram.puml b/docs/diagrams/TimetableUiHeaderSequenceDiagram.puml new file mode 100644 index 00000000000..ddb4d0f795e --- /dev/null +++ b/docs/diagrams/TimetableUiHeaderSequenceDiagram.puml @@ -0,0 +1,40 @@ +@startuml +!include style.puml + + +box TimetableUi UI_COLOR_T1 +participant ":TimetablePanel" as TimetablePanel UI_COLOR_T3 +participant ":TimetableHeaderLabel" as TimetableHeaderLabel UI_COLOR_T3 +participant ":TimetableHeaderTiming" as TimetableHeaderTiming UI_COLOR_T3 +end box + + +create TimetablePanel +[-[hidden]-> TimetablePanel +activate TimetablePanel + +group sd buildHeader +TimetablePanel -> TimetablePanel : buildHeader(List) +activate TimetablePanel + +create TimetableHeaderLabel +TimetablePanel -> TimetableHeaderLabel +activate TimetableHeaderLabel +TimetableHeaderLabel --> TimetablePanel +deactivate TimetableHeaderLabel + +loop For every 30 mins from start to end time of List +create TimetableHeaderTiming +TimetablePanel -> TimetableHeaderTiming +activate TimetableHeaderTiming +TimetableHeaderTiming --> TimetablePanel +deactivate TimetableHeaderTiming +end + + +TimetablePanel --> TimetablePanel +deactivate TimetablePanel +end + + +@enduml diff --git a/docs/diagrams/TimetableUiSequenceDiagram.puml b/docs/diagrams/TimetableUiSequenceDiagram.puml new file mode 100644 index 00000000000..7a19f55f68e --- /dev/null +++ b/docs/diagrams/TimetableUiSequenceDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include style.puml + + +box MainWindow UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +end box + +box TimetableUi UI_COLOR_T1 +participant ":TimetablePanel" as TimetablePanel UI_COLOR_T3 +end box + +[-> MainWindow : fillInnerParts() +activate MainWindow + +create TimetablePanel +MainWindow -> TimetablePanel : new TimetablePanel(List) +activate TimetablePanel + +TimetablePanel -> TimetablePanel : build() +activate TimetablePanel + +ref over TimetablePanel, TimetablePanel: buildHeader + +ref over TimetablePanel, TimetablePanel: buildDays + +opt UI_COLOR_T1 has classes +ref over TimetablePanel, TimetablePanel: buildClasses + +end + +TimetablePanel --> TimetablePanel +deactivate TimetablePanel + +TimetablePanel --> MainWindow +[<-- MainWindow +deactivate MainWindow +@enduml diff --git a/docs/diagrams/TimetableUiSlotSequenceDiagram.puml b/docs/diagrams/TimetableUiSlotSequenceDiagram.puml new file mode 100644 index 00000000000..d6c0625ba19 --- /dev/null +++ b/docs/diagrams/TimetableUiSlotSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include style.puml + + +box TimetableUi UI_COLOR_T1 +participant ":TimetablePanel" as TimetablePanel UI_COLOR_T3 +participant ":TimetableTuitionClassSlot" as TimetableTuitionClassSlot UI_COLOR_T3 +participant ":TimetableEmptySlot" as TimetableEmptySlot UI_COLOR_T3 +end box + + +create TimetablePanel +[-[hidden]-> TimetablePanel +activate TimetablePanel + +group sd buildClasses +TimetablePanel -> TimetablePanel : buildClasses(List) +activate TimetablePanel + +loop Until end of List + +opt Free time in between this and previous class +create TimetableEmptySlot +TimetablePanel -> TimetableEmptySlot +activate TimetableEmptySlot +TimetableEmptySlot --> TimetablePanel +deactivate TimetableEmptySlot +end + +create TimetableTuitionClassSlot +TimetablePanel -> TimetableTuitionClassSlot +activate TimetableTuitionClassSlot +TimetableTuitionClassSlot --> TimetablePanel +deactivate TimetableTuitionClassSlot +end + + +TimetablePanel --> TimetablePanel +deactivate TimetablePanel +end + + +@enduml diff --git a/docs/diagrams/TuitionClassModelClassDiagram.puml b/docs/diagrams/TuitionClassModelClassDiagram.puml new file mode 100644 index 00000000000..856c4e8c9a4 --- /dev/null +++ b/docs/diagrams/TuitionClassModelClassDiagram.puml @@ -0,0 +1,27 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package TuitionClass <>{ + +Class TuitionClass +Class ClassName +Class ClassTiming +Class Rate +Class Location +Class StudentNameList + +} + +Class HiddenOutside #FFFFFF +HiddenOutside ...> " * all"TuitionClass +HiddenOutside ...> " * filtered"TuitionClass + +TuitionClass *--> "1" ClassName +TuitionClass *--> "1" ClassTiming +TuitionClass *--> "1" Rate +TuitionClass *--> "1" Location +TuitionClass *--> "1" StudentNameList +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index ecae4876432..1cd89a61bbc 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -1,4 +1,4 @@ -@startuml + @startuml !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor UI_COLOR_T4 @@ -6,17 +6,28 @@ skinparam classBackgroundColor UI_COLOR package UI <>{ Interface Ui <> +Interface CommandObserver <> Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +'Class StudentListPanel +'Class StudentCard Class StatusBarFooter Class CommandBox +Enum TabName <> + +package TimetableUi <> { +} + +package ClassesUi <> { } +package StudentsUi <> { +} + +} package Model <> { Class HiddenModel #FFFFFF } @@ -28,33 +39,48 @@ Class HiddenLogic #FFFFFF Class HiddenOutside #FFFFFF HiddenOutside ..> Ui -UiManager .left.|> Ui +UiManager .right.|> Ui UiManager -down-> "1" MainWindow +MainWindow .right.|> CommandObserver MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel -MainWindow *-down-> "1" StatusBarFooter -MainWindow --> "0..1" HelpWindow - -PersonListPanel -down-> "*" PersonCard +MainWindow -down-> "0..1" HelpWindow -MainWindow -left-|> UiPart +'MainWindow *-down-> "1" StudentListPanel +MainWindow *-down-> "1" TimetableUi +MainWindow *-down-> "1" ClassesUi +MainWindow *-down-> "1" StudentsUi +MainWindow *-down-> "1" StatusBarFooter +'StudentListPanel -down-> "*" StudentCard +MainWindow -down-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +'StudentListPanel --|> UiPart +'StudentCard --|> UiPart +ClassesUi --|> UiPart +TimetableUi --|> UiPart +StudentsUi --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model -UiManager -right-> Logic -MainWindow -left-> Logic +'StudentCard .right.> Model +UiManager -up-> Logic +MainWindow -up-> Logic -PersonListPanel -[hidden]left- HelpWindow +Logic ---down-> CommandObserver : < Observes +Logic .down.> TabName + +TimetableUi .up.> Model +ClassesUi .up.> Model +StudentsUi .up.> Model + +'StudentListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter MainWindow -[hidden]-|> UiPart + +MainWindow .right.> TabName @enduml diff --git a/docs/diagrams/ViewSequenceDiagram.puml b/docs/diagrams/ViewSequenceDiagram.puml new file mode 100644 index 00000000000..e5f6a75a007 --- /dev/null +++ b/docs/diagrams/ViewSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TimesTableParser" as TimesTableParser LOGIC_COLOR +participant ":ViewCommandParser" as ViewCommandParser LOGIC_COLOR +participant "v:ViewCommand" as ViewCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box UI UI_COLOR_T1 +participant "mainWindow:CommandObserver" as CommandObserver UI_COLOR +end box + +[-> LogicManager : execute("view timetable") +activate LogicManager + +LogicManager -> TimesTableParser : parseCommand("view timetable") +activate TimesTableParser + +create ViewCommandParser +TimesTableParser -> ViewCommandParser +activate ViewCommandParser + +ViewCommandParser --> TimesTableParser +deactivate ViewCommandParser + +TimesTableParser -> ViewCommandParser : parse("timetable") +activate ViewCommandParser + +create ViewCommand +ViewCommandParser -> ViewCommand +activate ViewCommand + +ViewCommand --> ViewCommandParser : v +deactivate ViewCommand + +ViewCommandParser --> TimesTableParser : v +deactivate ViewCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ViewCommandParser -[hidden]-> TimesTableParser +destroy ViewCommandParser + +TimesTableParser --> LogicManager : v +deactivate TimesTableParser + +LogicManager -> ViewCommand : execute() +activate ViewCommand + +ViewCommand -> CommandObserver : updateView(TIMETABLE) +activate CommandObserver + +CommandObserver --> ViewCommand +deactivate CommandObserver + +create CommandResult +ViewCommand -> CommandResult +activate CommandResult + +CommandResult --> ViewCommand +deactivate CommandResult +'Hidden arrow to position the destroy marker below the end of the activation bar. +'CommandResult -[hidden]-> TimesTableParser +'destroy CommandResult + +ViewCommand --> LogicManager : result +deactivate ViewCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +'ViewCommand -[hidden]-> TimesTableParser +'destroy ViewCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/plantuml/FindXYZ.puml b/docs/diagrams/plantuml/FindXYZ.puml new file mode 100644 index 00000000000..26aac77b3a3 --- /dev/null +++ b/docs/diagrams/plantuml/FindXYZ.puml @@ -0,0 +1,11 @@ +@startuml +":Logic" -> ":FindTagCommand" : execute() +activate ":FindTagCommand" +":FindTagCommand" -> ":Model" : updateFilteredPersonList(predicate) +activate ":Model" +":Model" -> "filteredStudents:FilteredList" : setPredicate(predicate) +activate "filteredStudents:FilteredList" +return +return +return +@enduml diff --git a/docs/images/AddClassModelSequenceDiagram.png b/docs/images/AddClassModelSequenceDiagram.png new file mode 100644 index 00000000000..7f084df7182 Binary files /dev/null and b/docs/images/AddClassModelSequenceDiagram.png differ diff --git a/docs/images/AddClassRef1.png b/docs/images/AddClassRef1.png new file mode 100644 index 00000000000..27b7a330a0a Binary files /dev/null and b/docs/images/AddClassRef1.png differ diff --git a/docs/images/AddClassSequenceDiagram.png b/docs/images/AddClassSequenceDiagram.png new file mode 100644 index 00000000000..279ff975b01 Binary files /dev/null and b/docs/images/AddClassSequenceDiagram.png differ diff --git a/docs/images/AddToClassCreationSequenceDiagram.png b/docs/images/AddToClassCreationSequenceDiagram.png new file mode 100644 index 00000000000..c9564cbd225 Binary files /dev/null and b/docs/images/AddToClassCreationSequenceDiagram.png differ diff --git a/docs/images/AddToClassExecutionSequenceDiagram.png b/docs/images/AddToClassExecutionSequenceDiagram.png new file mode 100644 index 00000000000..d5913c5093e Binary files /dev/null and b/docs/images/AddToClassExecutionSequenceDiagram.png differ diff --git a/docs/images/AddToClassRef1.png b/docs/images/AddToClassRef1.png new file mode 100644 index 00000000000..d5e6f4ca4bc Binary files /dev/null and b/docs/images/AddToClassRef1.png differ diff --git a/docs/images/AddToClassRef2.png b/docs/images/AddToClassRef2.png new file mode 100644 index 00000000000..f179d8ebc32 Binary files /dev/null and b/docs/images/AddToClassRef2.png differ diff --git a/docs/images/AddToClassSequenceDiagram.png b/docs/images/AddToClassSequenceDiagram.png new file mode 100644 index 00000000000..6035103bdd6 Binary files /dev/null and b/docs/images/AddToClassSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..07ceafba692 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BuildTimetableDayActivityDiagram-Activity__Build_TimetableDay.png b/docs/images/BuildTimetableDayActivityDiagram-Activity__Build_TimetableDay.png new file mode 100644 index 00000000000..4fe7e7cefc3 Binary files /dev/null and b/docs/images/BuildTimetableDayActivityDiagram-Activity__Build_TimetableDay.png differ diff --git a/docs/images/BuildTimetableHeaderActivityDiagram-Activity__Build_TimetableHeader.png b/docs/images/BuildTimetableHeaderActivityDiagram-Activity__Build_TimetableHeader.png new file mode 100644 index 00000000000..75a8942b866 Binary files /dev/null and b/docs/images/BuildTimetableHeaderActivityDiagram-Activity__Build_TimetableHeader.png differ diff --git a/docs/images/BuildTimetableOverallDiagram.png b/docs/images/BuildTimetableOverallDiagram.png new file mode 100644 index 00000000000..187fc4a0fbc Binary files /dev/null and b/docs/images/BuildTimetableOverallDiagram.png differ diff --git a/docs/images/BuildTimetableTuitionClassSlotsActivityDiagram-Activity__Build_TimetableTuitionClassSlots.png b/docs/images/BuildTimetableTuitionClassSlotsActivityDiagram-Activity__Build_TimetableTuitionClassSlots.png new file mode 100644 index 00000000000..4efcfd187a7 Binary files /dev/null and b/docs/images/BuildTimetableTuitionClassSlotsActivityDiagram-Activity__Build_TimetableTuitionClassSlots.png differ diff --git a/docs/images/ClassList.png b/docs/images/ClassList.png new file mode 100644 index 00000000000..23f89b5e988 Binary files /dev/null and b/docs/images/ClassList.png differ diff --git a/docs/images/ClassPanelDiagram.png b/docs/images/ClassPanelDiagram.png new file mode 100644 index 00000000000..ec39669efe3 Binary files /dev/null and b/docs/images/ClassPanelDiagram.png differ diff --git a/docs/images/ClassPanelImage.png b/docs/images/ClassPanelImage.png new file mode 100644 index 00000000000..f1e14fad26c Binary files /dev/null and b/docs/images/ClassPanelImage.png differ diff --git a/docs/images/ClassesUiSequenceDiagram.png b/docs/images/ClassesUiSequenceDiagram.png new file mode 100644 index 00000000000..5372ebe3f71 Binary files /dev/null and b/docs/images/ClassesUiSequenceDiagram.png differ diff --git a/docs/images/CommandObserverPattern.png b/docs/images/CommandObserverPattern.png new file mode 100644 index 00000000000..ee66aee06e1 Binary files /dev/null and b/docs/images/CommandObserverPattern.png differ diff --git a/docs/images/DeleteClassRef1.png b/docs/images/DeleteClassRef1.png new file mode 100644 index 00000000000..bc4462fbe44 Binary files /dev/null and b/docs/images/DeleteClassRef1.png differ diff --git a/docs/images/DeleteClassSequenceDiagram.png b/docs/images/DeleteClassSequenceDiagram.png new file mode 100644 index 00000000000..ca410e6a8a9 Binary files /dev/null and b/docs/images/DeleteClassSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..9bf764c27cf 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DownloadJar.png b/docs/images/DownloadJar.png new file mode 100644 index 00000000000..54313f44c86 Binary files /dev/null and b/docs/images/DownloadJar.png differ diff --git a/docs/images/FindEarliestAndLatestHourActivityDiagram-Activity__Find_earliest_start_hour_and_latest_end_hour.png b/docs/images/FindEarliestAndLatestHourActivityDiagram-Activity__Find_earliest_start_hour_and_latest_end_hour.png new file mode 100644 index 00000000000..79f574b78cc Binary files /dev/null and b/docs/images/FindEarliestAndLatestHourActivityDiagram-Activity__Find_earliest_start_hour_and_latest_end_hour.png differ diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 00000000000..91dbdb0ae04 Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ diff --git a/docs/images/InvoiceExample.png b/docs/images/InvoiceExample.png new file mode 100644 index 00000000000..a5f2e7d0b5e Binary files /dev/null and b/docs/images/InvoiceExample.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index c3028aa1cda..d7fef19808a 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 39d7aec4b33..ae068c4a010 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index 58ad22ce16a..22c1a6c7a3c 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/RemoveFromClassCommandSequenceDiagram.png b/docs/images/RemoveFromClassCommandSequenceDiagram.png new file mode 100644 index 00000000000..06a746c7d65 Binary files /dev/null and b/docs/images/RemoveFromClassCommandSequenceDiagram.png differ diff --git a/docs/images/RemoveFromClassOverviewDiagram.png b/docs/images/RemoveFromClassOverviewDiagram.png new file mode 100644 index 00000000000..3fb4672bc39 Binary files /dev/null and b/docs/images/RemoveFromClassOverviewDiagram.png differ diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png new file mode 100644 index 00000000000..331634cf850 Binary files /dev/null and b/docs/images/SortSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagramWTuitionClass.png b/docs/images/StorageClassDiagramWTuitionClass.png new file mode 100644 index 00000000000..d027f312ca8 Binary files /dev/null and b/docs/images/StorageClassDiagramWTuitionClass.png differ diff --git a/docs/images/StudentModelClassDiagram.png b/docs/images/StudentModelClassDiagram.png new file mode 100644 index 00000000000..68c925218fd Binary files /dev/null and b/docs/images/StudentModelClassDiagram.png differ diff --git a/docs/images/StudentsDiagram.png b/docs/images/StudentsDiagram.png new file mode 100644 index 00000000000..73e6ab83df3 Binary files /dev/null and b/docs/images/StudentsDiagram.png differ diff --git a/docs/images/TimestableLogo.png b/docs/images/TimestableLogo.png new file mode 100644 index 00000000000..ae8acc32efe Binary files /dev/null and b/docs/images/TimestableLogo.png differ diff --git a/docs/images/TimestableMacStartUp.png b/docs/images/TimestableMacStartUp.png new file mode 100644 index 00000000000..172dc4e6c6f Binary files /dev/null and b/docs/images/TimestableMacStartUp.png differ diff --git a/docs/images/Timetable.png b/docs/images/Timetable.png new file mode 100644 index 00000000000..9491b9da384 Binary files /dev/null and b/docs/images/Timetable.png differ diff --git a/docs/images/TimetableAnnotation.png b/docs/images/TimetableAnnotation.png new file mode 100644 index 00000000000..5e00dcd8fdc Binary files /dev/null and b/docs/images/TimetableAnnotation.png differ diff --git a/docs/images/TimetableDiagram.png b/docs/images/TimetableDiagram.png new file mode 100644 index 00000000000..b771a014aa8 Binary files /dev/null and b/docs/images/TimetableDiagram.png differ diff --git a/docs/images/TimetableUiDaySequenceDiagram.png b/docs/images/TimetableUiDaySequenceDiagram.png new file mode 100644 index 00000000000..4baacfba0dc Binary files /dev/null and b/docs/images/TimetableUiDaySequenceDiagram.png differ diff --git a/docs/images/TimetableUiHeaderSequenceDiagram.png b/docs/images/TimetableUiHeaderSequenceDiagram.png new file mode 100644 index 00000000000..5dffc12a6f5 Binary files /dev/null and b/docs/images/TimetableUiHeaderSequenceDiagram.png differ diff --git a/docs/images/TimetableUiSequenceDiagram.png b/docs/images/TimetableUiSequenceDiagram.png new file mode 100644 index 00000000000..8dc4453fe29 Binary files /dev/null and b/docs/images/TimetableUiSequenceDiagram.png differ diff --git a/docs/images/TimetableUiSlotSequenceDiagram.png b/docs/images/TimetableUiSlotSequenceDiagram.png new file mode 100644 index 00000000000..ae66ffd9cef Binary files /dev/null and b/docs/images/TimetableUiSlotSequenceDiagram.png differ diff --git a/docs/images/TuitionClassModelClassDiagram.png b/docs/images/TuitionClassModelClassDiagram.png new file mode 100644 index 00000000000..e1a886b13bd Binary files /dev/null and b/docs/images/TuitionClassModelClassDiagram.png differ diff --git a/docs/images/TutorialImage.png b/docs/images/TutorialImage.png new file mode 100644 index 00000000000..2ec03308696 Binary files /dev/null and b/docs/images/TutorialImage.png differ diff --git a/docs/images/TutorialImageFive.png b/docs/images/TutorialImageFive.png new file mode 100644 index 00000000000..8df6807217c Binary files /dev/null and b/docs/images/TutorialImageFive.png differ diff --git a/docs/images/TutorialImageFour.png b/docs/images/TutorialImageFour.png new file mode 100644 index 00000000000..5108ab3fb9e Binary files /dev/null and b/docs/images/TutorialImageFour.png differ diff --git a/docs/images/TutorialImageOne.png b/docs/images/TutorialImageOne.png new file mode 100644 index 00000000000..247eff54bd8 Binary files /dev/null and b/docs/images/TutorialImageOne.png differ diff --git a/docs/images/TutorialImageThree.png b/docs/images/TutorialImageThree.png new file mode 100644 index 00000000000..21e9af157ea Binary files /dev/null and b/docs/images/TutorialImageThree.png differ diff --git a/docs/images/TutorialImageTwo.png b/docs/images/TutorialImageTwo.png new file mode 100644 index 00000000000..b97ce7a5cec Binary files /dev/null and b/docs/images/TutorialImageTwo.png differ diff --git a/docs/images/UGCommandExamples/AddClassCommand.png b/docs/images/UGCommandExamples/AddClassCommand.png new file mode 100644 index 00000000000..49c4debf75a Binary files /dev/null and b/docs/images/UGCommandExamples/AddClassCommand.png differ diff --git a/docs/images/UGCommandExamples/AddCommand.png b/docs/images/UGCommandExamples/AddCommand.png new file mode 100644 index 00000000000..97dcab7098d Binary files /dev/null and b/docs/images/UGCommandExamples/AddCommand.png differ diff --git a/docs/images/UGCommandExamples/AddToClassCommand.png b/docs/images/UGCommandExamples/AddToClassCommand.png new file mode 100644 index 00000000000..d816ed69ad4 Binary files /dev/null and b/docs/images/UGCommandExamples/AddToClassCommand.png differ diff --git a/docs/images/UGCommandExamples/ClassCommand.png b/docs/images/UGCommandExamples/ClassCommand.png new file mode 100644 index 00000000000..ace217d7f1e Binary files /dev/null and b/docs/images/UGCommandExamples/ClassCommand.png differ diff --git a/docs/images/UGCommandExamples/ClearCommand.png b/docs/images/UGCommandExamples/ClearCommand.png new file mode 100644 index 00000000000..88d98e38576 Binary files /dev/null and b/docs/images/UGCommandExamples/ClearCommand.png differ diff --git a/docs/images/UGCommandExamples/DeleteClassCommand.png b/docs/images/UGCommandExamples/DeleteClassCommand.png new file mode 100644 index 00000000000..baaae97a3ae Binary files /dev/null and b/docs/images/UGCommandExamples/DeleteClassCommand.png differ diff --git a/docs/images/UGCommandExamples/DeleteCommand.png b/docs/images/UGCommandExamples/DeleteCommand.png new file mode 100644 index 00000000000..602e23e4b1f Binary files /dev/null and b/docs/images/UGCommandExamples/DeleteCommand.png differ diff --git a/docs/images/UGCommandExamples/EditClassCommand.png b/docs/images/UGCommandExamples/EditClassCommand.png new file mode 100644 index 00000000000..9ce693fdf83 Binary files /dev/null and b/docs/images/UGCommandExamples/EditClassCommand.png differ diff --git a/docs/images/UGCommandExamples/EditCommand1.png b/docs/images/UGCommandExamples/EditCommand1.png new file mode 100644 index 00000000000..0f491c42880 Binary files /dev/null and b/docs/images/UGCommandExamples/EditCommand1.png differ diff --git a/docs/images/UGCommandExamples/EditCommand2.png b/docs/images/UGCommandExamples/EditCommand2.png new file mode 100644 index 00000000000..da70e3849a8 Binary files /dev/null and b/docs/images/UGCommandExamples/EditCommand2.png differ diff --git a/docs/images/UGCommandExamples/FindClassCommand1.png b/docs/images/UGCommandExamples/FindClassCommand1.png new file mode 100644 index 00000000000..930fc603976 Binary files /dev/null and b/docs/images/UGCommandExamples/FindClassCommand1.png differ diff --git a/docs/images/UGCommandExamples/FindClassCommand2.png b/docs/images/UGCommandExamples/FindClassCommand2.png new file mode 100644 index 00000000000..635ee5e7227 Binary files /dev/null and b/docs/images/UGCommandExamples/FindClassCommand2.png differ diff --git a/docs/images/UGCommandExamples/FindClassCommand3.png b/docs/images/UGCommandExamples/FindClassCommand3.png new file mode 100644 index 00000000000..9a7d435139d Binary files /dev/null and b/docs/images/UGCommandExamples/FindClassCommand3.png differ diff --git a/docs/images/UGCommandExamples/FindClassNameCommand.png b/docs/images/UGCommandExamples/FindClassNameCommand.png new file mode 100644 index 00000000000..efcda435857 Binary files /dev/null and b/docs/images/UGCommandExamples/FindClassNameCommand.png differ diff --git a/docs/images/UGCommandExamples/FindNameCommand.png b/docs/images/UGCommandExamples/FindNameCommand.png new file mode 100644 index 00000000000..9d0a49fd794 Binary files /dev/null and b/docs/images/UGCommandExamples/FindNameCommand.png differ diff --git a/docs/images/UGCommandExamples/FindTagCommand.png b/docs/images/UGCommandExamples/FindTagCommand.png new file mode 100644 index 00000000000..281a871713f Binary files /dev/null and b/docs/images/UGCommandExamples/FindTagCommand.png differ diff --git a/docs/images/UGCommandExamples/HelpCommand.png b/docs/images/UGCommandExamples/HelpCommand.png new file mode 100644 index 00000000000..5f4067dbd8e Binary files /dev/null and b/docs/images/UGCommandExamples/HelpCommand.png differ diff --git a/docs/images/UGCommandExamples/ListClassCommand.png b/docs/images/UGCommandExamples/ListClassCommand.png new file mode 100644 index 00000000000..cca3c4802cc Binary files /dev/null and b/docs/images/UGCommandExamples/ListClassCommand.png differ diff --git a/docs/images/UGCommandExamples/ListCommand.png b/docs/images/UGCommandExamples/ListCommand.png new file mode 100644 index 00000000000..6a709bc3032 Binary files /dev/null and b/docs/images/UGCommandExamples/ListCommand.png differ diff --git a/docs/images/UGCommandExamples/RemoveFromClassCommand.png b/docs/images/UGCommandExamples/RemoveFromClassCommand.png new file mode 100644 index 00000000000..2c91ce2d32e Binary files /dev/null and b/docs/images/UGCommandExamples/RemoveFromClassCommand.png differ diff --git a/docs/images/UGCommandExamples/SortCommand.png b/docs/images/UGCommandExamples/SortCommand.png new file mode 100644 index 00000000000..eee4eb78bce Binary files /dev/null and b/docs/images/UGCommandExamples/SortCommand.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample1.png b/docs/images/UGCommandExamples/TutorialExample1.png new file mode 100644 index 00000000000..daf4330030b Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample1.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample2.png b/docs/images/UGCommandExamples/TutorialExample2.png new file mode 100644 index 00000000000..fa280b45f39 Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample2.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample3.png b/docs/images/UGCommandExamples/TutorialExample3.png new file mode 100644 index 00000000000..1de22f13dad Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample3.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample4.png b/docs/images/UGCommandExamples/TutorialExample4.png new file mode 100644 index 00000000000..23f59dad85e Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample4.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample5.png b/docs/images/UGCommandExamples/TutorialExample5.png new file mode 100644 index 00000000000..346c57f0e78 Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample5.png differ diff --git a/docs/images/UGCommandExamples/TutorialExample6.png b/docs/images/UGCommandExamples/TutorialExample6.png new file mode 100644 index 00000000000..fb6997fd7aa Binary files /dev/null and b/docs/images/UGCommandExamples/TutorialExample6.png differ diff --git a/docs/images/UGCommandExamples/ViewCommand.png b/docs/images/UGCommandExamples/ViewCommand.png new file mode 100644 index 00000000000..d10496d0e93 Binary files /dev/null and b/docs/images/UGCommandExamples/ViewCommand.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 91488fd1a0f..c2641b7cc39 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 4bb8b2ce591..4d927be7dd4 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UpcomingFeature.png b/docs/images/UpcomingFeature.png new file mode 100644 index 00000000000..b7fd14c5b6d Binary files /dev/null and b/docs/images/UpcomingFeature.png differ diff --git a/docs/images/ViewSequenceDiagram.png b/docs/images/ViewSequenceDiagram.png new file mode 100644 index 00000000000..2bf3180951f Binary files /dev/null and b/docs/images/ViewSequenceDiagram.png differ diff --git a/docs/images/bernardwan.png b/docs/images/bernardwan.png new file mode 100644 index 00000000000..9e4f54480fb Binary files /dev/null and b/docs/images/bernardwan.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png index 235da1c273e..c70525a61dc 100644 Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..c0f7e28c13a 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/ongzl.png b/docs/images/ongzl.png new file mode 100644 index 00000000000..c0318165ae7 Binary files /dev/null and b/docs/images/ongzl.png differ diff --git a/docs/images/s7u4rt99.png b/docs/images/s7u4rt99.png new file mode 100644 index 00000000000..fb48872aadf Binary files /dev/null and b/docs/images/s7u4rt99.png differ diff --git a/docs/images/softmagnet.png b/docs/images/softmagnet.png new file mode 100644 index 00000000000..82707687c0a Binary files /dev/null and b/docs/images/softmagnet.png differ diff --git a/docs/images/yourally2.png b/docs/images/yourally2.png new file mode 100644 index 00000000000..86ff1bd7491 Binary files /dev/null and b/docs/images/yourally2.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..f935282114f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,48 @@ --- layout: page -title: AddressBook Level-3 +title: TimesTable --- +[![codecov](https://codecov.io/gh/AY2122S1-CS2103T-F11-1/tp/branch/master/graph/badge.svg?token=NNN2J2NDB2)](https://codecov.io/gh/AY2122S1-CS2103T-F11-1/tp) +[![CI Status](https://github.com/AY2122S1-CS2103T-F11-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-F11-1/tp/actions) -[![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) +![Ui](images/ClassList.png) + +**TimesTable is a desktop app for managing your tuition students and classes, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).** +While it has a GUI, it can be controlled by typing in commands using the CLI. +If you can type fast, TimesTable can get your class management tasks done faster than traditional GUI apps. + +* If you are interested in using TimesTable, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TimesTable, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. + +# Features + +### Student Management ![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). +TimesTable supports all basic contact management commands such as adding of students and their contact info, editing of contact info, +deleting students, finding students, sorting, etc. Furthermore, TimesTable also stores the Next-of-Kin(NOK) information of the student, +which is important and often used. This is displayed to the right of the student information. + +### Class Management + +![ClassList](images/ClassList.png) + +TimesTable allows users to easily create and manage their classes. The Class tab shows all the information +about your various classes at a glance, with the student information in the sidebar on the right. Users can +easily add and remove students from the various classes using the commands. The class list can also be filtered +to search for a specific class. + +### View Timetable -* 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. +![Timetable](images/Timetable.png) +TimesTable automatically generates a timetable for you based on the classes that you have added. +Simply head over to the Timetable tab by clicking on it or using the view command. **Acknowledgements** * Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +* The [`Timetable`](#view-timetable) feature was inspired by a similar feature in the past project of [Pet Store Helper](https://github.com/AY1920S2-CS2103-W15-4/main) ([DG](https://ay1920s2-cs2103-w15-4.github.io/main/DeveloperGuide.html#calendar-feature)). + The implementation of the components of the Timetable feature (`TimetableTuitionClassSlot`, `TimetableDay`, `TimetableRegion` and `TimetableEmptySlot`) has been adapted from them with maximum changes to fit our app. + The implementation of how we built and designed ([TimetablePanel.java](https://github.com/AY2122S1-CS2103T-F11-1/tp/blob/master/src/main/java/seedu/times/ui/timetabletab/TimetablePanel.java)) the entire Timetable Tab (layout, classes etc) is entirely new. diff --git a/docs/team/bernardwan.md b/docs/team/bernardwan.md new file mode 100644 index 00000000000..e806f83758e --- /dev/null +++ b/docs/team/bernardwan.md @@ -0,0 +1,81 @@ +--- +layout: page +title: Bernard Wan De Yuan's Project Portfolio Page +--- +### Project: TimesTable + +TimesTable is a desktop address book plus planner application for tuition teachers to keep track of their students +and classes. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, +and has about 25 kLoC. + +Given below are my contributions to the project. + + +* **New Feature**: Added the `removefromclass` command to remove one or more students from an existing class. + * Justification: A core feature to remove students that left. + * Highlights: The implementation of this command was especially challenging as the only information that the command has + are the student indices and the class index. These indices are based on the view displayed by the GUI, which is + affected by `sort` and `find` commands. Furthermore, only student names are stored in the `TuitionClass` object to reduce + update cascading. As such, the correct filters and sorting must be applied to the overall student list to obtain the student + names in the right order as displayed by the GUI. + * Pull requests: [\#117](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/117), [\#147](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/147), + [\#260](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/260), [\#266](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/266) + , [\#301](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/301) + + +* **New Feature**: Added the `deleteclass` command to delete existing tuition classes. + * Justification: A core feature for the user to get rid of older classes that they have finished teaching. + * Highlights: Deleting the entire class is much simpler than removing students from a class. The most challenging aspect + would be to ensure that the index of the class to be deleted corresponds to the actual class list order displayed on the GUI + after any `sort` or `find` commands. + * Pull requests: [\#104](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/104), [\#273](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/273) + + +* **New Feature**: Updated the behaviour of the `findname` and `findtag` command to support more natural use cases. + * What it does: Changed the functionality of the two `find` commands for students to support partial matches + and to allow for multi-word searches. + * Justification: The different types of `find` commands needs different logic as what the user will want to do varies. + For name searches, users would often want to search for first name together with last name. As such, the command should treat + the multi-word keyword as a single sequence. For the `findtag` command, users might search for just `math` and expect to find + all tags that contain math. As such, partial matching is required. + * Highlights: Previously, whitespace was used as a delimiter to obtain separate keywords. However, to use multi-word keywords, + another delimiter, a comma, was used. Also, the logic had to be adjusted to allow for partial matches, ignoring case. + * Pull requests: [\#145](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/145), [\#159](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/104), + [\#163](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/163) + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=totalCommits&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=bernardwan&tabRepo=AY2122S1-CS2103T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) + + +* **Enhancements to existing features**: + * Added some initial code for the class feature in the model - UniqueClassList and JsonAdaptedTuitionClass. ([\#104](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/104)) + * Wrote additional tests for existing features to increase test coverage ([\#260](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/260), [\#266](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/266), [\#273](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/273)) + * Updated the sample data to fill out the timetable, have more students with realistic data. ([\#145](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/145), [\#164](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/164)) + * Add tests for NOK feature ([\#66](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/66)) + * Added location field ([\#58](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/58)) + + +* **Documentation**: + * User Guide: + * Added section for location field. ([\#37](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/37)) + * Updated behavior for find features. ([\#145](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/145)) + * Update UG and examples for findname and findtag. ([\#163](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/163)) + * Added section for proposed feature of invoice generation. ([\#323](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/323)) + * Developer Guide: + * Updated NFRs section to include relevant information. ([\#12](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/12)) + * Added implementation details of the `removefromclass` feature. Added UML diagrams for the command. ([\#82](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/82), [\#301](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/301)) + +* **Contributions to team-based tasks** + * Set up project website: + * Set up github pages, changed settings, changed names and links from AddressBook to TimesTable + * Landing Page ([\#133](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/133) , [\#306](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/306)): + * Updated landing page to reflect TimesTable contents, added a section for each major feature, added pictures. + * Project Demo for 1.3: + * Recorded gifs and screenshots for different features. + * Helped maintained issue tracker: + * Created new issues, added milestones and assignees, closed relevant issues. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#303](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/303) + * Reported bugs for other teams during PE-D. [PE-D](https://github.com/bernardwan/ped/issues) + diff --git a/docs/team/ongzl.md b/docs/team/ongzl.md new file mode 100644 index 00000000000..3e8c4e19d40 --- /dev/null +++ b/docs/team/ongzl.md @@ -0,0 +1,95 @@ +--- +layout: page +title: Ong Zheng Lin's Project Portfolio Page +--- + +### Project: TimesTable + +TimesTable is a desktop class management application for tuition teachers to keep track of their students +and classes. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, +and has about 25 kLoC. + +Given below are my contributions to the project. + +* **New feature**: Added a schedule command that filters and sort students by their class timing (V1.2) + * What it does: Allows the user to filter students by day of which they are attending their class, so that user + can see who he is teaching and when. + * Justification: Provides early functionality to sort students by their class timing during the early phases of + the application. Provides some early work towards functions like `sort` and back-end processing of + `TuitionClass` and `ClassTiming`. + * Highlights: Provide a foundation for later iteration which improved the functionality of `TuitionClass` and `ClassTiming`. + * Pull request: [\#62](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/62), [\#71](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/71) + +* **New feature**: Added the basic GUI for the `TuitionClass list` in the `classes` tab + * What it does: Allows the user to view the classes currently in TimesTable. + * Justification: Allows the user to know the details of classes currently in TimesTable, so that they can have a + better idea of their workload and schedule. + * Highlights: Provide a foundation for later improvement and iterations in the `classes` tab. + * Commits: [1](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/e632b2de0b0f44bc67e84de601d8632e28696bf1) + +* **New feature** Added the backend support for `UniqueClassList` and `TuitionClass` + * What it does: Create support for adding and removing `TuitionClass` in `UniqueClassList` by ensuring no + overlap and proper updates from and to storage. + * Justification: Ensures that there is smooth reading of the JSON file into the model and overlapping class + timing between tuition classes are not allowed during editing and adding of class. + * Highlights: Provide support for `AddClassCommand`, `EditClassCommand` and safe reading of JSON file into model. + * Commits: [1](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/7ddac4e24d6abc6da6b669c0daf50a85601c3f5a), + [2](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/d344612297e2d21c988653b51a7eaa7d409cdacb), + [3](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/39d388fd3f0bf55dd3dcab9995d41ccf0419f0c6) + +* **Code contributed**: [Reposense](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=f11&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=Ongzl&tabRepo=AY2122S1-CS2103T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) + +* **Enhancements to existing features**: + * Link the `JSON` file and `UniqueClassList` in model, allowing the model to save (Commits + [1](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/7ddac4e24d6abc6da6b669c0daf50a85601c3f5a), + [2](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/d344612297e2d21c988653b51a7eaa7d409cdacb), + [3](https://github.com/AY2122S1-CS2103T-F11-1/tp/commit/39d388fd3f0bf55dd3dcab9995d41ccf0419f0c6)) + * Wrote additional tests for existing features to increase coverage (Pull request + [\#234](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/234), + [\#259](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/259), + [\#281](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/281)) + * Bug fixes for features `Class size` in `classes` tab and `Student list` in `classes` tab (Pull request + [\#106](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/106), + [\#137](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/137), + [\#246](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/246), + [\#249](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/249), + [\#251](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/251), + [\#252](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/252)) + * Fix old test cases which were outdated due to change in commands (Pull request [\#115](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/115), [\#155](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/155)) + +* **Documentation** + * User Guide: + * Added basic documentation for features `addclass`, `deleteclass`, `addtoclass`, `editclass`, `findclass`, + `listclass` [\#130](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/130) + * Added documentation on filtering `students` [\#281](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/281) + * Developer Guide + * Added use cases (Pull request + [\#17](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/17), + [\#265](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/265)) + * Updated `Storage` component and UML diagram [\#290](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/290) + * Added sequence diagrams and implementation detail for `AddClassCommand` and edited other UML diagrams (Pull request + [\#294](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/294), + [\#295](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/295), + [\#333](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/333)) + * Added challenges faced in `AddClassCommand` [\#344](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/344) + * Added documentation and sequence diagrams for `DeleteClassCommand` [\#320](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/320) + [\#333](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/333), + [\#339](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/339)) + * Added documentation and sequence diagrams for `DeleteClassCommand` (Pull request + [\#320](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/320), + [\#333](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/333)) + * Added method for manual testing of `deleteclass`, `removefromclass`, `findtag` and `view` [\#332](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/332) +* **Community** + * PRs reviewed (with non-trivial review comments): (Pull request + [\#26](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/26) + [\#40](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/40), + [\#47](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/47#partial-pull-merging), + [\#53](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/53), + [\#92](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/92), + [\#152](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/152), + [\#237](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/237), + [\#253](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/253), + [\#254](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/254), + [\#260](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/260)) + * Reported bugs for other teams during PE-D: [ped issues github](https://github.com/Ongzl/ped/issues) + diff --git a/docs/team/s7u4rt99.md b/docs/team/s7u4rt99.md new file mode 100644 index 00000000000..982ccd60a3f --- /dev/null +++ b/docs/team/s7u4rt99.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Stuart Long's Project Portfolio Page +--- + +### Project: TimesTable + +TimesTable is a Class Management desktop application for tuition teachers to keep track of their students +and classes. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, +and has about 25 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the skeleton of the tabs (Pull request: [\#73](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/73)) + * What it does: Added the base skeleton of the 3 tabs - `Students`, `Classes` and `Timetable` + * Highlights: I had to implement this before my teammates could start working on their respective tabs. This enhancement was the start of our evolution from the original AB3 into what we have today. It improved the entire UI and allowed us to have multiple tabs which users could view. + +* **New Feature**: Added the Timetable UI (Pull requests: [\#81](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/81), [\#97](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/97), [\#114](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/114), [\#136](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/136), [\#138](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/138)) + * What it does: Displays the tuition classes of the user in a timetable format in the `Timetable` Tab, improving the user experience. + * Justification: The Timetable allows user to see his entire weekly schedule in a glance. + * Highlights: This enhancement revamped the entire UI of the app. It required an in-depth analysis of all possible design patterns to upgrade the UI. The implementation was challenging as it required addition of many new components to build the entire timetable component. + * Credits: AY19/20 S2 CS2103-W15-4 [Github](https://github.com/AY1920S2-CS2103-W15-4/main/tree/master/src/main/java/clzzz/helper/ui/calendar) + +* **New Feature**: Added the `sort` feature (Pull requests: [\#126](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/126), [\#129](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/129), [\#132](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/132), [\#240](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/240), [\#250](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/250)) + * What it does: Sorts the students and classes based on the specified parameter in the specified order. + * Justification: User is able to arrange the list of students and/or classes in the order the user would like. It is also one of the basic features an address book application should have. + * Highlights: Implementing this `sort` command was relatively easier than the Timetable UI. However, the challenging part was choosing what parameter to allow the user to sort the students or classes by. The parameters I allowed to be sorted by were the student's name and the class' timing, as I believe that these are the two parameters which the user would sort them by the most. + +* **New Feature**: Added the `view` feature using Observer Pattern (Pull request: [\#92](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/92), [\#126](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/126)) + * What it does: Opens the tab specified by the user. + * Justification: This feature was crucial as it tailored our app towards users who do not like using the mouse or voice commands. The user can now switch tabs without the need of a mouse. This feature also played an important role in other commands. The commands related to TuitionClass, such as `addToClass`, `editClass` etc used this feature to switch to the `Classes` tab when they were called. Similarly, commands related to Student, such as `add`, `edit` etc used this feature to switch to the `Students` Tab. + * Highlights: Implementing this `view` command required an in-depth analysis of the possible design patterns. I decided to use the Observer pattern here. In this case, the `MainWindow` implemented the `CommandObserver` and observed the `Command`s. `CommandObserver#updateView()` switches the tab of the Observers to the tab indicated by the user. + +* **Code contributed**: [Reposense](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=s7u4rt99&tabRepo=AY2122S1-CS2103T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **Project management**: + * Managed releases `v1.3.trial`, `v1.3(final)` and `v1.4` on GitHub. + +* **Enhancements to existing features**: + * Added the field names to all StudentCard (Pull requests [\#68](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/68)) + * Wrote tests to increase code coverage (Pull requests [\#81](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/81), [\#92](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/92), [\#126](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/126), [\#136](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/136), [\#240](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/240), [\#255](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/255), [\#256](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/256)) + +* **Documentation**: + * User Guide: + * Added documentation for the `CLASS_TIMING` field, and the `sort`, `view` and `class` commands: [\#29](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/29), [\#146](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/146) + * Added command summary for `addclass`, `addtoclass`, `deleteclass`, `editclass`, `findname`, `findclass`, `findclassname`, `findtag`, `list`, `listclass`, `selectclass`, `sort`, `view`: [\#146](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/146) + * Reordered User Guide to follow the order which the user uses the app to improve the reader's experience: [\#150](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/150) + * Did cosmetic tweaks to existing documentation of features `delete`, `deleteclass`, `listclass`, `editclass`, `findname`, `findtag` and `findclassname`: [\#146](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/146) + * Developer Guide: + * Added target user profile: [\#25](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/25) + * Added UML diagrams and implementation details of the `Timetable` feature, `View` feature, `Sort` feature and `Observer` feature: [\#94](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/94), [\#297](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/297), [\#310](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/310), [\#325](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/325) + * Improved class diagram of Ui and Logic Component: [\#297](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/297), [\#310](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/310) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#31](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/31), [\#36](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/36), [\#37](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/37), [\#56](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/56), [\#58](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/58), [\#62](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/62), [\#71](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/71), [\#147](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/147), [\#254](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/254), [\#258](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/258), [\#281](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/281) + * Reported bugs and suggestions for other teams during PE-D: [PE-D issues github](https://github.com/s7u4rt99/ped/issues) + * Recorded 17 bugs, top 10% of cohort diff --git a/docs/team/softmagnet.md b/docs/team/softmagnet.md new file mode 100644 index 00000000000..ddb59f95733 --- /dev/null +++ b/docs/team/softmagnet.md @@ -0,0 +1,77 @@ +--- +layout: page +title: Lin Zhiwei's Project Portfolio Page +--- +### Project: TimesTable + +TimesTable is a desktop address book plus planner application for tuition teachers to keep track of their students +and classes. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, +and has about 25 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Integrated the `TuitionClass` into the model + * What it does: serves as the backbone for our application as our application needs a tuition class entity in its + model. + * Justification: Same as above, it serves as backbone for our project. + * Highlights: I wrote the initial setup for tuition class and added new classes to model a tuition class's +attributes such as StudentNameList. I made the design decision for how we keep track of students in classes so as to +minimize dependency. + * PRs: [\#56](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/56), + [\#60](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/60), + [\#104](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/104), + [\#127](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/127), + [\#139](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/139), + [\#151](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/151), + [\#152](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/152)... + +* **New Feature**: Added numerous basic commands related to tuition class such as `addclass`, `editclass` +and `addtoclass` + * What it does: adds the basic functionality of being able to interact with tuition classes such as adding a class, +editing a class and adding students to classes. + * Justification: The functionalities mentioned above are the basic features that our application should have given our +goal and target user. + * Highlights: Add commands and edit commands are relatively easier than addtoclass command which require designing +a new command format. I ended deciding to use indices of currently displayed list as it is the most convenient and +intuitive to use. + * PRs: [\#70](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/70), + [\#104](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/104), + [\#127](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/127), + [\#160](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/160), + [\#237](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/237), + [\#253](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/253), + [\#263](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/263)... + +* **New Feature/Enhancements to existing features**: greatly enhanced the search capabiltiy of our application by +adding `findtag`, `findclass` and `findclassname` commands + * What it does: allows user to find students by the tags they have and also find classes by class timing and class +name. + * Justification: Users should be able to have such search functionalities as mentioned above to find quickly students +or classes they are interested in. + * Highlights: Adding find commands for each different attributes have various nuances that one has to look out for and +I have learnt to be more careful around these nuances. + * PRs: [\#70](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/70), + [\#112](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/112), + [\#158](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/158)... + +* **Code contributed**: [Reposense](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabAuthor=softmagnet&tabRepo=AY2122S1-CS2103T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&tabType=authorship) + +* **Documentation** + * User Guide: Added some documentation for updated features + * PRs: [\#170](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/170), + [\#254](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/254) + * Developer Guide (DG): Added DG for find, addtoclass commands and updated DG for model component + * PRs: [\#88](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/88), + [\#293](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/293), + [\#308](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/308), + [\#312](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/312), + [\#326](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/326)... + +* **Community** + * PRs reviewed (with non-trivial review comments): [\#232](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/232), + [\#117](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/117), + [\#53](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/53), + [\#25](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/25), + [\#17](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/17) + + * Reported bugs for other teams during PE-D: [ped issues github](https://github.com/softmagnet/ped) diff --git a/docs/team/yourally2.md b/docs/team/yourally2.md new file mode 100644 index 00000000000..229bf319fcb --- /dev/null +++ b/docs/team/yourally2.md @@ -0,0 +1,66 @@ +--- +layout: page +title: Kevin Chua's Project Portfolio Page +--- + +### Project: TimesTable + +TimesTable is a Class Management desktop application for tuition teachers to keep track of their students +and classes. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, +and has about 25 kLoC. + +Given below are my contributions to the project. + +* **New feature**: Added the GUI and related behaviour for the class tab (Pull Request: [\#103](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/103)) + * What it does: Allows the user to visually see which students belong to each respective class by double clicking on the class + or using a command `class INDEX` which is described in another section. + * Justification: Visuals allows the user to quickly tell which students belong to a class at a glance. + Forms the backbone of the class feature as all interactions with the class is done in this tab. + * Highlights: I gained a deep understanding of how the GUI functioned together with greater understanding of different design patterns. + +* **New feature**: Add `class` feature to view a specific class without double clicking (Pull Request: [\#103](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/103)) + * What it does: It shows the students belonging to the specified class. Does the same thing as double clicking on a class. + * Justification: It allows us to perform the same functionality without using a mouse, which is important. + * Highlights: It was challenging to modify the GUI using a command as it meant a lot of coupling, so after discussing with Stuart we decided to follow the Observer pattern, + which proved to be very useful. + +* **New feature**: Overloaded `edit` and set up base for overloading `add` command to allow user to add the next-of-kin (nok) of the Student. (Pull Requests: [\#26](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/26) [\#47](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/47)) + * What it does: Allows the user to add/edit a Student's nok with name, address, and location when adding/editing a Student. + * Highlights: This enhancement went through several iterations as we initially implemented the + `nok` command to specifically to add/edit an nok, but settled on overloading the `add` and `edit` command because it would + be faster and less confusing to add an `nok` and the Student at once. + +* **New feature**: Store next-of-kin (nok) information in a nested json (Pull Request: [\#65](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/65)) + * Highlights: I decided to nest the json in a key `nok` instead of having fields like `nok_name`, `nok_phone` etc so it is clearer what the fields are for. Nesting json objects proved to be more complex compared to strings. + +* **New feature**: Improved colours for the GUI overall (Pull Request: [\#217](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/217)) + * Highlights: Finding the right combination of colors that the entire team agreed on proved to be more difficult than expected and the colors went through several iterations before the team could decide on a color. In the process, finding the css for specific components to change was surprisingly harder than expected as the documentation for the css in javafx is often unclear. + +* **Code contributed**: [Reposense](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=yourally2&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=yourally2&tabRepo=AY2122S1-CS2103T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **Enhancements to existing features**: + * Make Student and next-of-kin (NOK) inherit from Person to implement DRY principles (Pull Request [\#30](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/30)) + * Limit length and number of tags to prevent GUI from covering other information and added tests (Pull Request [\#319](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/319)) + * Wrote tests for `FindClassCommandParser`, `FindClassCommand`, `SelectClassCommand` and `SelectClassCommandParser` (Pull Request [\#258](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/258) [\#302](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/302)) + * Bug fixes for GUI, `class` command, Student list in `classes` tab and `addtoclass` command (Pull Request + [\#248](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/248), + [\#243](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/243), + [\#304](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/304)) + +* **Documentation** + * User Guide: + * Ensured entire UG flowed well (Pull Request [\#224](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/224), [\#303](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/303)) + * Added section for proposed feature of attendance marking in Payment Management feature. ([Commit](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/319/commits/effd6fbe0d67a6ed821fa16581e7ed3fd3e9cd32)) + * Developer Guide + * Added UML class diagram, Sequence Diagram for Ui component and the ClassesUi component (Pull Request [\#291](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/291) [\#309](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/309)) + * Added noteworthy implementation details for `nok/` field (Pull Request [\#292](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/292), [\#291](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/291)) + * Added manual testing for `addtoclass`, `listclass`, `delete`, `sort` commands (Pull Request [\#327](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/327) [\#86](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/86)) + * Added user stories (Pull Request [\#22](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/22)) + +* **Community** + * PRs reviewed (with non-trivial review comments): (Pull Request + [\#256](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/256), + [\#81](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/81), + [\#50](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/50), + [\#311](https://github.com/AY2122S1-CS2103T-F11-1/tp/pull/311) + * Reported bugs for other teams during PE-D: [ped issues github](https://github.com/yourally2/ped/issues) diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 8919d8eaa17..64aaf9a00a7 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -23,9 +23,9 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu **`RemarkCommand.java`:** ``` java -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import seedu.address.model.Model; +import seedu.times.model.Model; /** * Changes the remark of an existing person in the address book. @@ -91,7 +91,7 @@ Let’s change `RemarkCommand` to parse input from the user. We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended. ``` java -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { //... @@ -142,7 +142,7 @@ Your code should look something like [this](https://github.com/se-edu/addressboo Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user. -Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface. +Create a `RemarkCommandParser` class in the `seedu.times.logic.parser` package. The class must extend the `Parser` interface. ![The relationship between Parser and RemarkCommandParser](../images/add-remark/ParserInterface.png) @@ -229,7 +229,7 @@ Now that we have all the information that we need, let’s lay the groundwork fo ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.times.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-af2f075d24dfcd333876f0fbce321f25). Note how `Remark` has no constrains and thus does not require input validation. @@ -242,7 +242,7 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe). +Simply add the following to [`seedu.times.ui.studenttab.StudentCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe). **`PersonCard.java`:** @@ -295,7 +295,7 @@ While the changes to code may be minimal, the test data will have to be updated
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! +:exclamation: You must delete AddressBook’s storage file located at `/data/timestable.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..5b718e73164 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. +The `address` field in `Person` is actually an instance of the `seedu.times.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. * :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) @@ -96,7 +96,7 @@ At this point, your application is working as intended and all your tests are pa In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate. -**`invalidPersonAddressBook.json`:** +**`invalidPersonTimesTable.json`:** ```json { diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..91bac22457d 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -39,7 +39,7 @@ In our case, we would want to begin the tracing at the very point where the App -According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`. +According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.times.logic.Logic`. @@ -48,7 +48,7 @@ According to the sequence diagram you saw earlier (and repeated above for refere :bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. +A quick look at the `seedu.times.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. ```java public interface Logic { @@ -190,7 +190,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ public CommandResult execute(Model model) throws CommandException { ... Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person editedPerson = createEditedPerson(personToEdit, editStudentDescriptor); if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java deleted file mode 100644 index 64f18992160..00000000000 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ /dev/null @@ -1,20 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Represents a command with hidden internal logic and the ability to be executed. - */ -public abstract class Command { - - /** - * Executes the command and returns the result message. - * - * @param model {@code Model} which the command should operate on. - * @return feedback message of the operation result for display - * @throws CommandException If an error occurs during command execution. - */ - public abstract CommandResult execute(Model model) throws CommandException; - -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book 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 " - + "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"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -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. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.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; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - 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_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())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -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.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * 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)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} 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); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} 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); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} 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); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list 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. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - 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()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - 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 deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index 79868290974..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,79 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. - */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/times/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/times/AppParameters.java index ab552c398f3..e6b0ddc6c08 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/times/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.times; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/times/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/seedu/times/Main.java index 052a5068631..0ccb791c394 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/times/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.times; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/times/MainApp.java similarity index 71% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/times/MainApp.java index 4133aaa0151..0e8ca0517bd 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/times/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.times; import java.io.IOException; import java.nio.file.Path; @@ -7,36 +7,36 @@ import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.times.commons.core.Config; +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.core.Version; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.commons.util.ConfigUtil; +import seedu.times.commons.util.StringUtil; +import seedu.times.logic.Logic; +import seedu.times.logic.LogicManager; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; +import seedu.times.model.util.SampleDataUtil; +import seedu.times.storage.JsonTimesTableStorage; +import seedu.times.storage.JsonUserPrefsStorage; +import seedu.times.storage.Storage; +import seedu.times.storage.StorageManager; +import seedu.times.storage.TimesTableStorage; +import seedu.times.storage.UserPrefsStorage; +import seedu.times.ui.Ui; +import seedu.times.ui.UiManager; /** * Runs the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing TimesTable ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +56,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + TimesTableStorage timesTableStorage = new JsonTimesTableStorage(userPrefs.getTimesTableFilePath()); + storage = new StorageManager(timesTableStorage, userPrefsStorage); initLogging(config); @@ -74,20 +74,20 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional timesTableOptional; + ReadOnlyTimesTable initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + timesTableOptional = storage.readTimesTable(); + if (!timesTableOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample TimesTable"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = timesTableOptional.orElseGet(SampleDataUtil::getSampleTimesTable); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty TimesTable"); + initialData = new TimesTable(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty TimesTable"); + initialData = new TimesTable(); } return new ModelManager(initialData, userPrefs); @@ -151,7 +151,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TimesTable"); initializedPrefs = new UserPrefs(); } @@ -167,13 +167,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TimesTable " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping TimesTable ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/times/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/times/commons/core/Config.java index 91145745521..f41d64fadd1 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/times/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/times/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/times/commons/core/GuiSettings.java index ba33653be67..f94111a4d32 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/times/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/times/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/times/commons/core/LogsCenter.java index 431e7185e76..5603edaeebe 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/times/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import java.io.IOException; import java.util.Arrays; @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "timestable.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/times/commons/core/Messages.java b/src/main/java/seedu/times/commons/core/Messages.java new file mode 100644 index 00000000000..b587cbcfc40 --- /dev/null +++ b/src/main/java/seedu/times/commons/core/Messages.java @@ -0,0 +1,17 @@ +package seedu.times.commons.core; + +/** + * Container for user visible messages. + */ +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_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid"; + public static final String MESSAGE_INVALID_CLASS_DISPLAYED_INDEX = "The class index provided is invalid"; + public static final String MESSAGE_CLASHING_CLASS_TIMING = "The operation aborted because it will introduce a clash" + + " in class timing."; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_CLASSES_LISTED_OVERVIEW = "%1$d classes listed!"; + public static final String MESSAGE_NO_SEARCH_TERMS_ENTERED = "You have not entered any search term!"; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/times/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/times/commons/core/Version.java index 12142ec1e32..e5c2382ceb4 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/times/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/times/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/times/commons/core/index/Index.java index 19536439c09..6ead768d156 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/times/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.times.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/times/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/times/commons/exceptions/DataConversionException.java index 1f689bd8e3f..f5cee7e4b1d 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/times/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.times.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/times/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/times/commons/exceptions/IllegalValueException.java index 19124db485c..9658fff87e5 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/times/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.times.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/times/commons/util/AppUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/times/commons/util/AppUtil.java index 87aa89c0326..da3201aa0cc 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/times/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.times.MainApp; /** * A container for App specific utility functions @@ -39,4 +39,14 @@ public static void checkArgument(Boolean condition, String errorMessage) { throw new IllegalArgumentException(errorMessage); } } + + /** + * Checks that {@code test} doesn't exceed {@code maxLength}. Part of argument validation. + */ + public static boolean isWithinLength(String test, int maxLength) { + if (test.length() <= maxLength) { + return true; + } + return false; + } } diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/times/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/times/commons/util/CollectionUtil.java index eafe4dfd681..2e2a7763d4b 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/times/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/times/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/times/commons/util/ConfigUtil.java index f7f8a2bd44c..68668c9e6ba 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/times/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.times.commons.core.Config; +import seedu.times.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/times/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/times/commons/util/FileUtil.java index b1e2767cdd9..86511f30490 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/times/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/times/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/times/commons/util/JsonUtil.java index 8ef609f055d..9d47884fb12 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/times/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/times/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/times/commons/util/StringUtil.java index 61cc8c9a1cb..5c4a4def8dd 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/times/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/times/logic/CommandObserver.java b/src/main/java/seedu/times/logic/CommandObserver.java new file mode 100644 index 00000000000..b9d2b2fb3a2 --- /dev/null +++ b/src/main/java/seedu/times/logic/CommandObserver.java @@ -0,0 +1,28 @@ +package seedu.times.logic; + +import seedu.times.ui.TabName; + +/** + * Interface to represent the observers of command class. + */ +public interface CommandObserver { + + /** + * Updates the view of the observer. + * + * @param tabToView tab to change the view to. + */ + public void updateView(TabName tabToView); + + /** + * Updates the class selected of the observer. + * + * @param indexOfClassToSelect class index to change the selected to. + */ + public void updateClass(Integer indexOfClassToSelect); + + /** + * Hides the tuition class student list. + */ + public void hideTuitionClassStudentList(); +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/times/logic/Logic.java similarity index 56% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/seedu/times/logic/Logic.java index 92cd8fa605a..37e94cb67fa 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/times/logic/Logic.java @@ -1,14 +1,15 @@ -package seedu.address.logic; +package seedu.times.logic; import java.nio.file.Path; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.times.commons.core.GuiSettings; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; /** * API of the Logic component @@ -24,19 +25,19 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the TimesTable. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.times.model.Model#getTimesTable() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyTimesTable getTimesTable(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredStudentList(); /** * Returns the user prefs' address book file path. */ - Path getAddressBookFilePath(); + Path getTimesTableFilePath(); /** * Returns the user prefs' GUI settings. @@ -47,4 +48,9 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + + ObservableList getFilteredTuitionClassList(); + + void setCommandObserver(CommandObserver commandObserver); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/times/logic/LogicManager.java similarity index 50% rename from src/main/java/seedu/address/logic/LogicManager.java rename to src/main/java/seedu/times/logic/LogicManager.java index 9d9c6d15bdc..ee89e18d7db 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/times/logic/LogicManager.java @@ -1,21 +1,22 @@ -package seedu.address.logic; +package seedu.times.logic; import java.io.IOException; import java.nio.file.Path; import java.util.logging.Logger; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.core.LogsCenter; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.logic.parser.TimesTableParser; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.Model; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.storage.Storage; /** * The main LogicManager of the app. @@ -26,7 +27,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final TimesTableParser timesTableParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,7 +35,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + timesTableParser = new TimesTableParser(); } @Override @@ -42,11 +43,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = timesTableParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveTimesTable(model.getTimesTable()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -55,18 +56,28 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public void setCommandObserver(CommandObserver commandObserver) { + Command.setCommandObserver(commandObserver); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ReadOnlyTimesTable getTimesTable() { + return model.getTimesTable(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + return model.getFilteredTuitionClassList(); + } + + @Override + public Path getTimesTableFilePath() { + return model.getTimesTableFilePath(); } @Override diff --git a/src/main/java/seedu/times/logic/commands/AddCommand.java b/src/main/java/seedu/times/logic/commands/AddCommand.java new file mode 100644 index 00000000000..56076c47754 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/AddCommand.java @@ -0,0 +1,84 @@ +package seedu.times.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NOK; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.person.Student; +import seedu.times.ui.TabName; + +/** + * Adds a person to the Timestable. + */ +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.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_NOK + " " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS \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 + "Chemistry " + + PREFIX_TAG + "Sec 3 \n" + + PREFIX_NOK + " " + + PREFIX_NAME + "Jack Doe " + + PREFIX_PHONE + "10987654 " + + PREFIX_EMAIL + "jackd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "; + + public static final String MESSAGE_SUCCESS = "New Student added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + private final Student studentToAdd; + + /** + * Creates an AddCommand to add the specified {@code Person}. + * We always pass in a new {@code TuitionClass}, and check if it alr exists + */ + public AddCommand(Student student) { + requireNonNull(student); + studentToAdd = student; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasStudent(studentToAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.addStudent(studentToAdd); + + updateView(TabName.STUDENTS); + + return new CommandResult(String.format(MESSAGE_SUCCESS, studentToAdd)); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCommand // instanceof handles nulls + && studentToAdd.equals(((AddCommand) other).studentToAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/times/logic/commands/ClearCommand.java similarity index 53% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/seedu/times/logic/commands/ClearCommand.java index 9c86b1fa6e4..16547dac3ff 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/times/logic/commands/ClearCommand.java @@ -1,23 +1,23 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; +import seedu.times.model.Model; +import seedu.times.model.TimesTable; /** - * Clears the address book. + * Clears the Timestable. */ 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 = "TimesTable has been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.setAddressBook(new AddressBook()); + model.setTimesTable(new TimesTable()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/times/logic/commands/Command.java b/src/main/java/seedu/times/logic/commands/Command.java new file mode 100644 index 00000000000..fd86a00fe3f --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/Command.java @@ -0,0 +1,61 @@ +package seedu.times.logic.commands; + +import java.util.ArrayList; + +import seedu.times.logic.CommandObserver; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.ui.TabName; + +/** + * Represents a command with hidden internal logic and the ability to be executed. + */ +public abstract class Command { + + private static final ArrayList commandObservers = new ArrayList<>(); + + public static void setCommandObserver(CommandObserver commandObserver) { + commandObservers.add(commandObserver); + } + + /** + * Updates the CommandObservers by calling the updateView method for each of them. + * + * @param tabToView Tab to be viewed. + */ + public void updateView(TabName tabToView) { + for (CommandObserver commandObserver : commandObservers) { + commandObserver.updateView(tabToView); + } + } + + /** + * Updates the CommandObservers by calling the updateClass command. + * + * @param indexOfClassToSelect index of class to be selected. + */ + public void updateClass(Integer indexOfClassToSelect) { + for (CommandObserver commandObserver : commandObservers) { + commandObserver.updateClass(indexOfClassToSelect); + } + } + + /** + * Updates the CommandObservers by calling the hideTuitionClassStudentList command. + */ + public void hideTuitionClassStudentList() { + for (CommandObserver commandObserver : commandObservers) { + commandObserver.hideTuitionClassStudentList(); + } + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + public abstract CommandResult execute(Model model) throws CommandException; + +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/times/logic/commands/CommandResult.java similarity index 97% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/times/logic/commands/CommandResult.java index 92f900b7916..20ad800806f 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/times/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/times/logic/commands/DeleteCommand.java similarity index 71% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/times/logic/commands/DeleteCommand.java index 02fd256acba..ddc22fc9664 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/times/logic/commands/DeleteCommand.java @@ -1,17 +1,17 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static java.util.Objects.requireNonNull; import java.util.List; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.person.Student; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a person identified using it's displayed index from the Timestable. */ public class DeleteCommand extends Command { @@ -33,15 +33,15 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Student studentToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteStudent(studentToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, studentToDelete)); } @Override diff --git a/src/main/java/seedu/times/logic/commands/EditCommand.java b/src/main/java/seedu/times/logic/commands/EditCommand.java new file mode 100644 index 00000000000..f62f2a21765 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/EditCommand.java @@ -0,0 +1,296 @@ +package seedu.times.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.commons.util.CollectionUtil; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; +import seedu.times.ui.TabName; + +/** + * Edits the details of an existing person in the Timestable. + */ +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 student identified " + + "by the index number used in the displayed student list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + public static final String MESSAGE_EDIT_STUDENT_SUCCESS = "Edited Student: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the address book."; + + private final Index index; + private final EditStudentDescriptor editStudentDescriptor; + + /** + * @param index of the person in the filtered person list to edit + * @param editStudentDescriptor details to edit the person with + */ + public EditCommand(Index index, EditStudentDescriptor editStudentDescriptor) { + requireNonNull(index); + requireNonNull(editStudentDescriptor); + + this.index = index; + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + + //execute update scade, change name in student name list in tuitionclass + editStudentDescriptor.getName().ifPresent(name -> { + model.updateClassStudentLists(name, studentToEdit.getName()); + }); + + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + + + if (!studentToEdit.isSamePerson(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + updateView(TabName.STUDENTS); + + return new CommandResult(String.format(MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent)); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) + throws CommandException { + assert studentToEdit != null; + + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(studentToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Address updatedAddress = editStudentDescriptor.getAddress().orElse(studentToEdit.getAddress()); + Set updatedTags = editStudentDescriptor.getTags().orElse(studentToEdit.getTags()); + + // Nok + Name nokName = editStudentDescriptor.getNokName().orElse(studentToEdit.getNok().getName()); + Phone nokPhone = editStudentDescriptor.getNokPhone().orElse(studentToEdit.getNok().getPhone()); + Email nokEmail = editStudentDescriptor.getNokEmail().orElse(studentToEdit.getNok().getEmail()); + Address nokAddress = editStudentDescriptor.getNokAddress().orElse(studentToEdit.getNok().getAddress()); + Nok nok = new Nok(nokName, nokPhone, nokEmail, nokAddress); + + return new Student(updatedName, updatedPhone, updatedEmail, updatedAddress, nok, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCommand)) { + return false; + } + + // state check + EditCommand e = (EditCommand) other; + return index.equals(e.index) + && editStudentDescriptor.equals(e.editStudentDescriptor); + } + + /** + * 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 EditStudentDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set tags; + + private Name nokName; + private Phone nokPhone; + private Email nokEmail; + private Address nokAddress; + + public EditStudentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditStudentDescriptor(EditStudentDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + + setNokName(toCopy.nokName); + setNokPhone(toCopy.nokPhone); + setNokEmail(toCopy.nokEmail); + setNokAddress(toCopy.nokAddress); + + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, address, tags, nokAddress, nokEmail, nokName, + nokPhone); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + //// nok + + public void setNokName(Name nokName) { + this.nokName = nokName; + } + + public Optional getNokName() { + return Optional.ofNullable(nokName); + } + public void setNokPhone(Phone nokPhone) { + this.nokPhone = nokPhone; + } + + public Optional getNokPhone() { + return Optional.ofNullable(nokPhone); + } + + public void setNokEmail(Email nokEmail) { + this.nokEmail = nokEmail; + } + + public Optional getNokEmail() { + return Optional.ofNullable(nokEmail); + } + + public void setNokAddress(Address nokAddress) { + this.nokAddress = nokAddress; + } + + public Optional
getNokAddress() { + return Optional.ofNullable(nokAddress); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentDescriptor)) { + return false; + } + + // state check + EditStudentDescriptor e = (EditStudentDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getAddress().equals(e.getAddress()) + && getTags().equals(e.getTags()) + && getNokAddress().equals(e.getNokAddress()) + && getNokEmail().equals(e.getNokEmail()) + && getNokName().equals(e.getNokName()) + && getNokPhone().equals(e.getNokPhone()); + + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/times/logic/commands/ExitCommand.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/times/logic/commands/ExitCommand.java index 3dd85a8ba90..f43e86b2b59 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/times/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import seedu.address.model.Model; +import seedu.times.model.Model; /** * Terminates the program. diff --git a/src/main/java/seedu/times/logic/commands/FindNameCommand.java b/src/main/java/seedu/times/logic/commands/FindNameCommand.java new file mode 100644 index 00000000000..96cd371ded3 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/FindNameCommand.java @@ -0,0 +1,44 @@ +package seedu.times.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.times.commons.core.Messages; +import seedu.times.model.Model; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.times.ui.TabName; + +/** + * Finds and lists all persons in Timestable whose name contains any of the search terms. + * Keyword matching is case insensitive. + */ +public class FindNameCommand extends Command { + + public static final String COMMAND_WORD = "findname"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain all of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: NAME [, [NAME]...]\n" + + "Example: " + COMMAND_WORD + " alice, bob lim, charlie"; + + private final NameContainsKeywordsPredicate predicate; + + public FindNameCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredStudentList(predicate); + updateView(TabName.STUDENTS); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredStudentList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindNameCommand // instanceof handles nulls + && predicate.equals(((FindNameCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/times/logic/commands/FindTagCommand.java b/src/main/java/seedu/times/logic/commands/FindTagCommand.java new file mode 100644 index 00000000000..309224e8ed6 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/FindTagCommand.java @@ -0,0 +1,46 @@ +package seedu.times.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.times.commons.core.Messages; +import seedu.times.model.Model; +import seedu.times.model.person.predicates.TagsContainsKeywordsPredicate; +import seedu.times.ui.TabName; + +/** + * Finds and lists all persons in Timestable whose list of tags contains any of the search terms. + * Keyword matching is case insensitive. + */ +public class FindTagCommand extends Command { + + public static final String COMMAND_WORD = "findtag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose list of tags contains any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [, [KEYWORD]...]\n" + + "Example: " + COMMAND_WORD + " Maths, Physics"; + + private final TagsContainsKeywordsPredicate predicate; + + public FindTagCommand(TagsContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredStudentList(predicate); + + updateView(TabName.STUDENTS); + + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredStudentList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTagCommand // instanceof handles nulls + && predicate.equals(((FindTagCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/times/logic/commands/HelpCommand.java similarity index 88% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/times/logic/commands/HelpCommand.java index bf824f91bd0..b45c6ef0e45 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/times/logic/commands/HelpCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import seedu.address.model.Model; +import seedu.times.model.Model; /** * Format full help instructions for every command for display. diff --git a/src/main/java/seedu/times/logic/commands/ListCommand.java b/src/main/java/seedu/times/logic/commands/ListCommand.java new file mode 100644 index 00000000000..d06a3ee3842 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/ListCommand.java @@ -0,0 +1,26 @@ +package seedu.times.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.times.model.Model; +import seedu.times.ui.TabName; + +/** + * Lists all persons in the Timestable to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all students"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + updateView(TabName.STUDENTS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/times/logic/commands/SortCommand.java b/src/main/java/seedu/times/logic/commands/SortCommand.java new file mode 100644 index 00000000000..bf9c18e157c --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/SortCommand.java @@ -0,0 +1,95 @@ +package seedu.times.logic.commands; + +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.ui.TabName; + +/** + * Sorts either the students or tuition class in Timestable. + */ +public class SortCommand extends Command { + public static final String COMMAND_WORD = "sort"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts students/classes by specified" + + " parameter and order\n" + + "Parameters: PARAMETER_TO_SORT_BY DIRECTION_OF_SORT\n" + + "Example: " + COMMAND_WORD + " name asc"; + public static final String INVALID_SORTBY = "The parameter to sort by can only be: name or timing"; + public static final String INVALID_DIRECTIONOFSORT = "The direction of sort can only be asc or desc"; + public static final String MESSAGE_SUCCESS = "Sorted %s based on %s in %s direction"; + + // input keywords + public static final String SORT_BY_NAME = "name"; + public static final String SORT_BY_CLASS_TIMING = "timing"; + public static final String DIRECTION_OF_SORT_ASC = "asc"; + public static final String DIRECTION_OF_SORT_DESC = "desc"; + public static final String STUDENT_TAB_SORTED = "students"; + public static final String CLASSES_TAB_SORTED = "classes"; + + private final String sortBy; + + private final String directionOfSort; + + /** + * Constructs a new SortCommand. + * + * @param sortBy sortBy keyword. + * @param directionOfSort directionOfSort keyword. + */ + public SortCommand(String sortBy, String directionOfSort) { + this.sortBy = sortBy; + this.directionOfSort = directionOfSort; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + // sort the StudentNameList; + if (sortBy.equals(SORT_BY_NAME)) { + + if (directionOfSort.equals(DIRECTION_OF_SORT_ASC)) { + model.sortStudents((student1, student2) -> + student1.getName().toString().compareTo(student2.getName().toString())); + } else { + model.sortStudents((student1, student2) -> + student2.getName().toString().compareTo(student1.getName().toString())); + } + + updateView(TabName.STUDENTS); + + return new CommandResult(String.format(MESSAGE_SUCCESS, STUDENT_TAB_SORTED, sortBy, directionOfSort)); + + } else if (sortBy.equals(SORT_BY_CLASS_TIMING)) { + + if (directionOfSort.equals(DIRECTION_OF_SORT_ASC)) { + model.sortClasses((class1, class2) -> + class1.getClassTiming().compareTo(class2.getClassTiming())); + } else { + model.sortClasses((class1, class2) -> + class2.getClassTiming().compareTo(class1.getClassTiming())); + } + + updateView(TabName.CLASSES); + + hideTuitionClassStudentList(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, CLASSES_TAB_SORTED, sortBy, directionOfSort)); + } else { + return new CommandResult("sort by" + sortBy + + " has not been implemented by the developers"); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (this == obj) { + return true; + } else if (obj instanceof SortCommand) { + SortCommand sortCommand = (SortCommand) obj; + return this.directionOfSort.equals(sortCommand.directionOfSort) && this.sortBy.equals(sortCommand.sortBy); + } else { + return false; + } + } +} diff --git a/src/main/java/seedu/times/logic/commands/ViewCommand.java b/src/main/java/seedu/times/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..2185a77d9a2 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/ViewCommand.java @@ -0,0 +1,50 @@ +package seedu.times.logic.commands; + +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.ui.TabName; + +/** + * Sets the UI to View the tab selected. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Switches tab to specified tab\n" + + "Parameters: TAB_TO_VIEW\n" + + "Example: " + COMMAND_WORD + " timetable"; + public static final String INVALID_TAB = "This tab doesn't exists.\n" + + "You can only switch to students, timetable or classes."; + + private final TabName tabToView; + + /** + * Creates a ViewCommand to view the specified tab index. + */ + public ViewCommand(TabName tab) { + this.tabToView = tab; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + + updateView(tabToView); + + return new CommandResult("Successfully switched to " + tabToView + " tab", + false, false); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ViewCommand) { + ViewCommand viewCommand = (ViewCommand) obj; + return this.tabToView == viewCommand.tabToView; + } else { + return false; + } + } + +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/AddClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/AddClassCommand.java new file mode 100644 index 00000000000..fc7548fefa1 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/AddClassCommand.java @@ -0,0 +1,69 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASSTIMING; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASS_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.times.logic.parser.CliSyntax.PREFIX_RATE; + +import seedu.times.commons.core.Messages; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.exceptions.OverlappingClassException; +import seedu.times.ui.TabName; + +/** + * Adds a tuition class to Timestable. + */ +public class AddClassCommand extends Command { + + public static final String COMMAND_WORD = "addclass"; + + public static final String MESSAGE_SUCCESS = "New class added: %1$s"; + public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Adds a class to the address book.\n" + + "Parameters: " + + PREFIX_CLASS_NAME + "CLASS NAME " + + PREFIX_CLASSTIMING + "CLASS TIMING " + + PREFIX_RATE + "RATE " + + PREFIX_LOCATION + "LOCATION\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CLASS_NAME + "Sec 4 A Maths " + + PREFIX_CLASSTIMING + "MON 11:30-13:30 " + + PREFIX_RATE + "70 " + + PREFIX_LOCATION + "Nex Tuition Center"; + + private final TuitionClass tuitionClass; + + /** + * Creates an AddToClassCommand to add the specified tuitionClass. + * + * @param tuitionClass TuitionClass to be added. + */ + public AddClassCommand(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + this.tuitionClass = tuitionClass; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + try { + model.addTuitionClass(tuitionClass); + } catch (OverlappingClassException ice) { + throw new CommandException(Messages.MESSAGE_CLASHING_CLASS_TIMING); + } + + updateView(TabName.CLASSES); + return new CommandResult(String.format(MESSAGE_SUCCESS, tuitionClass)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddClassCommand // instanceof handles nulls + && tuitionClass.equals(((AddClassCommand) other).tuitionClass)); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/AddToClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/AddToClassCommand.java new file mode 100644 index 00000000000..bca3c65c7ec --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/AddToClassCommand.java @@ -0,0 +1,147 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import static seedu.times.logic.commands.classcommands.EditClassCommand.MESSAGE_DUPLICATE_STUDENT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.exceptions.DuplicateStudentInClassException; +import seedu.times.ui.TabName; + +/** + * Adds existing students to existing tuition class. + */ +public class AddToClassCommand extends Command { + + public static final String COMMAND_WORD = "addtoclass"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds students to a class by the index number used " + + "in the displayed class and student list in students tab.\n" + + "Indexes must be non-zero positive integers.\n" + + "Parameters: CLASS_INDEX " + + "STUDENT_INDEX...\n" + + "Example: " + COMMAND_WORD + " " + + "1 " + + "2 3 5 " + + "(adds students indexed 2, 3 and 5 to class indexed 1)"; + + public static final String MESSAGE_ADD_SUCCESS = "Successfully added students to class "; + public static final String NO_STUDENT_INDEX_PROVIDED_MESSAGE = "No student index is provided!"; + public static final String INVALID_OR_MISSING_CLASS_INDEX = "Class index is invalid or is not provided"; + public static final int CLASS_INDEX_POSITION = 0; + public static final int STUDENT_INDEX_STARTING_POSITION = 1; + + private final Index toEditClassIndex; + private final List studentIndices; + + /** + * Constructs AddToClassCommand. + * + * @param indexArray ArrayList of index. + */ + public AddToClassCommand(List indexArray) { + requireNonNull(indexArray); + + //the first index is the index of class in filtered class list that students would be added to + toEditClassIndex = indexArray.get(CLASS_INDEX_POSITION); + + //the remaining indices are those of the students in filtered student list + studentIndices = indexArray.subList(STUDENT_INDEX_STARTING_POSITION, indexArray.size()); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + //check indices are not out-of-range + List lastShownStudentList = model.getFilteredStudentList(); + List lastShownTuitionClassList = model.getFilteredTuitionClassList(); + checkIndicesAreInRange(lastShownStudentList, lastShownTuitionClassList); + + //get names to be added and class to add to + ArrayList namesToAdd = createNameList(studentIndices, lastShownStudentList); + TuitionClass classToAddTo = lastShownTuitionClassList.get(toEditClassIndex.getZeroBased()); + + //get updated student list + StudentNameList currentStudentNameList = classToAddTo.getStudentList(); + StudentNameList updatedStudentNameList = new StudentNameList(); + try { + updatedStudentNameList.addAll(currentStudentNameList); + updatedStudentNameList.addAll(namesToAdd); + } catch (DuplicateStudentInClassException e) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT + e.getMessage()); + } + + //create edit class descriptor + EditClassDescriptor editClassDescriptor = new EditClassDescriptor(); + editClassDescriptor.setStudentList(updatedStudentNameList); + + //swap out old tuition class with new tuition class + TuitionClass editedClass = EditClassCommand.createEditedClass(classToAddTo, editClassDescriptor); + model.setClass(classToAddTo, editedClass); + + // Switches the view to the class view and updates the class + updateView(TabName.CLASSES); + updateClass(toEditClassIndex.getZeroBased()); + + return new CommandResult(String.format(MESSAGE_ADD_SUCCESS, editedClass)); + } + + private ArrayList createNameList(List studentIndices, List lastShownStudentList) { + ArrayList nameList = new ArrayList<>(); + studentIndices.stream().forEach(index -> { + Student student = lastShownStudentList.get(index.getZeroBased()); + nameList.add(student.getName()); + }); + return nameList; + } + + private void checkIndicesAreInRange(List lastShownStudentList, List lastShownTuitionClass) + throws CommandException { + int studentListSize = lastShownStudentList.size(); + int classListSize = lastShownTuitionClass.size(); + + if (studentListSize == 0) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + assert studentListSize > 0; + assert classListSize >= 0; + + if (toEditClassIndex.getZeroBased() >= classListSize) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + for (Index index : studentIndices) { + if (index.getZeroBased() >= studentListSize) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof AddToClassCommand)) { + return false; + } + + AddToClassCommand other = ((AddToClassCommand) o); + return other.toEditClassIndex.equals(toEditClassIndex) + && other.studentIndices.equals(studentIndices); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/DeleteClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/DeleteClassCommand.java new file mode 100644 index 00000000000..0fadfc08fe1 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/DeleteClassCommand.java @@ -0,0 +1,77 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.TabName; + +/** + * Deletes a class from Timestable. + */ +public class DeleteClassCommand extends Command { + public static final String COMMAND_WORD = "deleteclass"; + + public static final String MESSAGE_SUCCESS = "Class deleted: %1$s"; + public static final String MESSAGE_MISSING_CLASS = "This class does not exist in the address book"; + + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the class identified by the index in class list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + private static Logger logger = Logger.getLogger("DeleteClassCommand"); + + private final Index index; + + /** + * Create a DeleteClassCommand with the specified index. + * @param index The index of the class to be deleted. + */ + public DeleteClassCommand(Index index) { + assert index.getOneBased() > 0; + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownClassList = model.getFilteredTuitionClassList(); + if (index.getOneBased() > lastShownClassList.size()) { + logger.log(Level.INFO, "There are only " + lastShownClassList.size() + " classes displayed"); + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + TuitionClass tuitionClass = lastShownClassList.get(index.getZeroBased()); + + if (!model.hasTuitionClass(tuitionClass)) { + throw new CommandException(MESSAGE_MISSING_CLASS); + } + + model.deleteTuitionClass(tuitionClass); + logger.log(Level.INFO, "Class deleted"); + + updateView(TabName.CLASSES); + + hideTuitionClassStudentList(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, tuitionClass)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteClassCommand // instanceof handles nulls + && index.equals(((DeleteClassCommand) other).index)); // state check + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/EditClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/EditClassCommand.java new file mode 100644 index 00000000000..e18ab446ca2 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/EditClassCommand.java @@ -0,0 +1,208 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASSTIMING; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASS_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.times.logic.parser.CliSyntax.PREFIX_RATE; + +import java.util.List; +import java.util.Optional; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.commons.util.CollectionUtil; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.exceptions.DuplicateClassException; +import seedu.times.model.tuitionclass.exceptions.OverlappingClassException; +import seedu.times.ui.TabName; + +/** + * Edits a class in Timestable. + */ +public class EditClassCommand extends Command { + + public static final String COMMAND_WORD = "editclass"; + + public static final String MESSAGE_EDIT_CLASS_SUCCESS = "Edited class: %1$s"; + public static final String MESSAGE_NO_FIELD_PROVIDED = "At least one field to editclass must be provided."; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in this class:\n"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the class identified " + + "by the index number used in the displayed person list " + + "(adding or removing students is done with separate commands!)\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_CLASS_NAME + "CLASS NAME] " + + "[" + PREFIX_CLASSTIMING + "CLASS TIMING] " + + "[" + PREFIX_RATE + "RATE] " + + "[" + PREFIX_LOCATION + "LOCATION]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_CLASSTIMING + "MON 13:30-15:30 " + + PREFIX_RATE + "100"; + + + private final Index index; + private final EditClassDescriptor editClassDescriptor; + + /** + * Constructs a new EditClassCommand. + * + * @param index of class to be edited. + * @param editClassDescriptor descriptor of new class. + */ + public EditClassCommand(Index index, EditClassDescriptor editClassDescriptor) { + requireAllNonNull(index, editClassDescriptor); + this.index = index; + this.editClassDescriptor = editClassDescriptor; + } + + /** + * Creates EditedClass. + * + * @param classToEdit to be editted. + * @param editClassDescriptor to replace. + * @return edited TuitionClass. + */ + public static TuitionClass createEditedClass(TuitionClass classToEdit, EditClassDescriptor editClassDescriptor) { + assert classToEdit != null; + + ClassName className = editClassDescriptor.getClassName().orElse(classToEdit.getClassName()); + ClassTiming classTiming = editClassDescriptor.getClassTiming().orElse(classToEdit.getClassTiming()); + Location location = editClassDescriptor.getLocation().orElse(classToEdit.getLocation()); + Rate rate = editClassDescriptor.getRate().orElse(classToEdit.getRate()); + StudentNameList studentNameList = editClassDescriptor.getStudentList().orElse(classToEdit.getStudentList()); + + return new TuitionClass(className, classTiming, location, rate, studentNameList); + + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownClassList = model.getFilteredTuitionClassList(); + if (index.getZeroBased() >= lastShownClassList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + TuitionClass classToEdit = lastShownClassList.get(index.getZeroBased()); + TuitionClass editedClass = createEditedClass(classToEdit, editClassDescriptor); + + try { + model.setClass(classToEdit, editedClass); + model.updateFilteredClassList(Model.PREDICATE_SHOW_ALL_CLASS); + } catch (OverlappingClassException | DuplicateClassException e) { + throw new CommandException(e.getMessage()); + } + + updateView(TabName.CLASSES); + return new CommandResult(String.format(MESSAGE_EDIT_CLASS_SUCCESS, editedClass)); + } + + /** + * Stores the names of the student and the class involved in an addToClassCommand. + */ + public static class EditClassDescriptor { + private ClassName className; + private ClassTiming classTiming; + private Location location; + private Rate rate; + private StudentNameList studentNameList; + + public EditClassDescriptor() { + } + + /** + * Constructs a copy of the given EditClassDescriptor. + * + * @param toCopy The EditClassDescriptor to copy. + */ + public EditClassDescriptor(EditClassDescriptor toCopy) { + setClassName(toCopy.className); + setClassTiming(toCopy.classTiming); + setLocation(toCopy.location); + setRate(toCopy.rate); + setStudentList(toCopy.studentNameList); + } + + public Optional getClassName() { + return Optional.ofNullable(className); + } + + public void setClassName(ClassName className) { + this.className = className; + } + + public Optional getClassTiming() { + return Optional.ofNullable(classTiming); + } + + public void setClassTiming(ClassTiming classTiming) { + this.classTiming = classTiming; + } + + public Optional getLocation() { + return Optional.ofNullable(location); + } + + public void setLocation(Location location) { + this.location = location; + } + + public Optional getRate() { + return Optional.ofNullable(rate); + } + + public void setRate(Rate rate) { + this.rate = rate; + } + + public Optional getStudentList() { + return Optional.ofNullable(studentNameList); + } + + public void setStudentList(StudentNameList studentNameList) { + this.studentNameList = studentNameList; + } + + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(className, classTiming, rate, location, studentNameList); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EditClassDescriptor)) { + return false; + } + + EditClassDescriptor e = (EditClassDescriptor) other; + return getClassName().equals(e.getClassName()) + && getClassTiming().equals(e.getClassTiming()) + && getRate().equals(e.getRate()) + && getLocation().equals(e.getLocation()) + && getStudentList().equals(e.getStudentList()); + } + } + + @Override + public boolean equals(Object o) { + return this == o + || ((o instanceof EditClassCommand) + && index.equals(((EditClassCommand) o).index) + && editClassDescriptor.equals(((EditClassCommand) o).editClassDescriptor)); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/FindClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/FindClassCommand.java new file mode 100644 index 00000000000..73ad4a6e938 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/FindClassCommand.java @@ -0,0 +1,60 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; + +import seedu.times.commons.core.Messages; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.predicates.ClassTimingContainsKeywordsPredicate; +import seedu.times.ui.TabName; + +/** + * Finds a tuition class in Timestable by its class timing. + */ +public class FindClassCommand extends Command { + + public static final String COMMAND_WORD = "findclass"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all classes whose class timing contains all of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Valid keywords are limited to:\n" + + " 1) 3 letter abbreviation for day of the week\n" + + " 2) time expressed in HH:MM-HH:MM format.\n" + + "(" + ClassTiming.MESSAGE_CONSTRAINTS + ")\n" + + "Parameters: CLASS_TIMING\n" + + "Example: " + COMMAND_WORD + " Mon 11:30-12:30"; + + private final ClassTimingContainsKeywordsPredicate predicate; + + /** + * Constructs a new FindClassCommand. + * + * @param predicate The predicate to filter the classes by. + */ + public FindClassCommand(ClassTimingContainsKeywordsPredicate predicate) { + requireNonNull(predicate); + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredClassList(predicate); + + updateView(TabName.CLASSES); + hideTuitionClassStudentList(); + + return new CommandResult( + String.format(Messages.MESSAGE_CLASSES_LISTED_OVERVIEW, model.getFilteredTuitionClassList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindClassCommand // instanceof handles nulls + && predicate.equals(((FindClassCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/FindClassNameCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/FindClassNameCommand.java new file mode 100644 index 00000000000..3fa440052bd --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/FindClassNameCommand.java @@ -0,0 +1,55 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; + +import seedu.times.commons.core.Messages; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.predicates.ClassNameContainsKeywordsPredicate; +import seedu.times.ui.TabName; + +/** + * Finds a tuition class in Timestable by its class name. + */ +public class FindClassNameCommand extends Command { + + public static final String COMMAND_WORD = "findclassname"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all classes whose class name contains all of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: CLASS_NAME [, [CLASS_NAME]...]\n" + + "Example: " + COMMAND_WORD + " sec4 physics"; + + private final ClassNameContainsKeywordsPredicate predicate; + + public FindClassNameCommand(ClassNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredClassList(predicate); + + updateView(TabName.CLASSES); + hideTuitionClassStudentList(); + + return new CommandResult( + String.format(Messages.MESSAGE_CLASSES_LISTED_OVERVIEW, model.getFilteredTuitionClassList().size())); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof FindClassNameCommand)) { + return false; + } + + return ((FindClassNameCommand) o).predicate.equals(predicate); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/ListClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/ListClassCommand.java new file mode 100644 index 00000000000..b43c1893f58 --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/ListClassCommand.java @@ -0,0 +1,27 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.model.Model.PREDICATE_SHOW_ALL_CLASS; + +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.model.Model; +import seedu.times.ui.TabName; + +/** + * Lists all tuition classes in the Timestable to the user. + */ +public class ListClassCommand extends Command { + + public static final String COMMAND_WORD = "listclass"; + + public static final String MESSAGE_SUCCESS = "Listed all classes"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + updateView(TabName.CLASSES); + model.updateFilteredClassList(PREDICATE_SHOW_ALL_CLASS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommand.java new file mode 100644 index 00000000000..4208ad6341d --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommand.java @@ -0,0 +1,141 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.TabName; + +/** + * Removes students from a tuition class. + */ +public class RemoveFromClassCommand extends Command { + public static final String COMMAND_WORD = "removefromclass"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes students from a class by the index" + + " number used " + + "in the displayed class and student list in classes tab.\n" + + "Parameters: CLASS_INDEX " + + "STUDENT_INDEX... (Indexes must be non-zero positive integers)\n" + + "Example: " + COMMAND_WORD + " " + + "2 " + + "3 4 5 " + + "(removes students indexed 3, 4 and 5 from class indexed 2)"; + + public static final String NO_STUDENT_INDEX_PROVIDED_MESSAGE = "No student index is provided!"; + public static final String MESSAGE_REMOVE_SUCCESS = "Successfully removed students from class "; + + private static Logger logger = Logger.getLogger("RemoveFromClassCommand"); + + private final Index toEditClassIndex; + private final List studentIndicesToRemove; + + /** + * Constructs a remove from class command. + * + * @param indexArray Array with index of class to remove from and index of students. + */ + public RemoveFromClassCommand(List indexArray) { + requireNonNull(indexArray); + + //the first index is the index of class in filtered class list that students would be removed from + toEditClassIndex = indexArray.get(0); + assert toEditClassIndex.getOneBased() > 0; + + //the remaining indices are those of the students in filtered student list + studentIndicesToRemove = indexArray.subList(1, indexArray.size()); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + //get class to remove from + List lastShownClassList = model.getFilteredTuitionClassList(); + if (lastShownClassList.size() == 0) { + logger.log(Level.INFO, "There are no classes displayed."); + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } else if (toEditClassIndex.getOneBased() > lastShownClassList.size()) { + logger.log(Level.INFO, "There are only " + lastShownClassList.size() + " classes displayed."); + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + TuitionClass classToRemoveFrom = lastShownClassList.get(toEditClassIndex.getZeroBased()); + + //get names to be removed + StudentNameList currentStudentNameList = classToRemoveFrom.getStudentList(); + List filteredStudentList = model.getFilteredStudentList().filtered(student -> + classToRemoveFrom.containsStudent(student.getName())); + checkIndicesAreValid(studentIndicesToRemove, filteredStudentList); + ArrayList namesToRemove = createNewNameList(studentIndicesToRemove, filteredStudentList); + + + //get updated student list + StudentNameList updatedStudentNameList = new StudentNameList(); + updatedStudentNameList.addAll(currentStudentNameList); + updatedStudentNameList.removeAll(namesToRemove); + + //create edit class descriptor + EditClassCommand.EditClassDescriptor editClassDescriptor = new EditClassCommand.EditClassDescriptor(); + editClassDescriptor.setStudentList(updatedStudentNameList); + + //swap out old tuition class with new tuition class + TuitionClass editedClass = EditClassCommand.createEditedClass(classToRemoveFrom, editClassDescriptor); + model.setClass(classToRemoveFrom, editedClass); + + // Switches the view to the class view and updates the class + updateView(TabName.CLASSES); + updateClass(toEditClassIndex.getZeroBased()); + + logger.log(Level.INFO, "Class name list updated."); + + return new CommandResult(String.format(MESSAGE_REMOVE_SUCCESS, editedClass)); + } + + private ArrayList createNewNameList(List studentIndices, List studentList) { + ArrayList newNameList = new ArrayList<>(); + studentIndices.stream().forEach(index -> { + Name name = studentList.get(index.getZeroBased()).getName(); + newNameList.add(name); + }); + return newNameList; + } + + private void checkIndicesAreValid(List studentIndices, List studentList) + throws CommandException { + int size = studentList.size(); + for (Index index : studentIndices) { + if (index.getZeroBased() >= size) { + logger.log(Level.INFO, "There are only " + size + " students displayed."); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + } + logger.log(Level.INFO, "All indices are valid."); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof RemoveFromClassCommand)) { + return false; + } + + RemoveFromClassCommand other = ((RemoveFromClassCommand) o); + return other.toEditClassIndex.equals(toEditClassIndex) + && other.studentIndicesToRemove.equals(studentIndicesToRemove); + } +} diff --git a/src/main/java/seedu/times/logic/commands/classcommands/SelectClassCommand.java b/src/main/java/seedu/times/logic/commands/classcommands/SelectClassCommand.java new file mode 100644 index 00000000000..548e056a7ba --- /dev/null +++ b/src/main/java/seedu/times/logic/commands/classcommands/SelectClassCommand.java @@ -0,0 +1,63 @@ +package seedu.times.logic.commands.classcommands; + +import java.util.List; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.TabName; + +/** + * Selects the class to view its students. + */ +public class SelectClassCommand extends Command { + public static final String COMMAND_WORD = "class"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects the appropriate class\n" + + "Parameters: CLASS_INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + public static final String INVALID_TAB = "This class doesn't exists.\n" + + "You can only switch to indexes shown in the Classlist."; + + private final Index targetIndex; + + + public SelectClassCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredTuitionClassList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + TuitionClass tuitionClass = lastShownList.get(targetIndex.getZeroBased()); + + // Switches the view to the class view and updates the class + updateView(TabName.CLASSES); + updateClass(targetIndex.getZeroBased()); + + return new CommandResult("Viewing class " + tuitionClass.toString(), + false, false); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof SelectClassCommand)) { + return false; + } + + SelectClassCommand other = ((SelectClassCommand) o); + return other.targetIndex.equals(targetIndex); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/times/logic/commands/exceptions/CommandException.java similarity index 89% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/times/logic/commands/exceptions/CommandException.java index a16bd14f2cd..0373bb6041d 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/times/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.times.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/times/logic/parser/AddCommandParser.java b/src/main/java/seedu/times/logic/parser/AddCommandParser.java new file mode 100644 index 00000000000..be6b5ccf2ac --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/AddCommandParser.java @@ -0,0 +1,90 @@ +package seedu.times.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.logic.parser.ParserUtil.arePrefixesPresent; + +import java.util.Set; + +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String args) throws ParseException { + requireNonNull(args); + + String argsBeforeNok = args; + String argsAfterNok = ""; + + if (args.contains("nok/")) { + String[] splitArgs = args.split("nok/"); + if (splitArgs.length != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + argsBeforeNok = splitArgs[0]; + argsAfterNok = splitArgs[1]; + } + + Student student = parseStudent(argsBeforeNok); + Nok nok = parseNok(argsAfterNok); + student.setNok(nok); + return new AddCommand(student); + } + + private Student parseStudent(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + return new Student(name, phone, email, address, null , tagList); + } + + private Nok parseNok(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + return new Nok( + ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()), + ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()), + ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()), + ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()) + ); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/times/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/times/logic/parser/ArgumentMultimap.java index 954c8e18f8e..a6e52db3d06 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/times/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/times/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/times/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..f5f37218720 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/times/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/times/logic/parser/CliSyntax.java similarity index 56% rename from src/main/java/seedu/address/logic/parser/CliSyntax.java rename to src/main/java/seedu/times/logic/parser/CliSyntax.java index 75b1a9bf119..8391d32856b 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/times/logic/parser/CliSyntax.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; /** * Contains Command Line Interface (CLI) syntax definitions common to multiple commands @@ -7,9 +7,14 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_CLASS_NAME = new Prefix("cn/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_RATE = new Prefix("r/"); + public static final Prefix PREFIX_NOK = new Prefix("nok/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_CLASSTIMING = new Prefix("ct/"); + public static final Prefix PREFIX_LOCATION = new Prefix("l/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/times/logic/parser/DeleteCommandParser.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/times/logic/parser/DeleteCommandParser.java index 522b93081cc..9af89a113e9 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/times/logic/parser/DeleteCommandParser.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.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.times.commons.core.index.Index; +import seedu.times.logic.commands.DeleteCommand; +import seedu.times.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object diff --git a/src/main/java/seedu/times/logic/parser/EditCommandParser.java b/src/main/java/seedu/times/logic/parser/EditCommandParser.java new file mode 100644 index 00000000000..58f6dd5f4ff --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/EditCommandParser.java @@ -0,0 +1,120 @@ +package seedu.times.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.logic.parser.ParserUtil.checkTagsAreValid; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.commands.EditCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditCommand parse(String args) throws ParseException { + requireNonNull(args); + + // Tokenize twice, once for everything before nok and everything after it + String argsBeforeNok = args; + String argsAfterNok = ""; + + if (args.contains("nok/")) { + String[] splitArgs = args.split("nok/"); + if (splitArgs.length != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + argsBeforeNok = splitArgs[0]; + argsAfterNok = splitArgs[1]; + } + + ArgumentMultimap argMultimapBeforeNok = + ArgumentTokenizer + .tokenize(argsBeforeNok, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + + ArgumentMultimap argMultimapAfterNok = + ArgumentTokenizer.tokenize(argsAfterNok, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimapBeforeNok.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + } + EditCommand.EditStudentDescriptor editStudentDescriptor = new EditCommand.EditStudentDescriptor(); + + // For before nok + if (argMultimapBeforeNok.getValue(PREFIX_NAME).isPresent()) { + editStudentDescriptor.setName(ParserUtil.parseName(argMultimapBeforeNok.getValue(PREFIX_NAME).get())); + } + if (argMultimapBeforeNok.getValue(PREFIX_PHONE).isPresent()) { + editStudentDescriptor.setPhone(ParserUtil.parsePhone(argMultimapBeforeNok.getValue(PREFIX_PHONE).get())); + } + if (argMultimapBeforeNok.getValue(PREFIX_EMAIL).isPresent()) { + editStudentDescriptor.setEmail(ParserUtil.parseEmail(argMultimapBeforeNok.getValue(PREFIX_EMAIL).get())); + } + if (argMultimapBeforeNok.getValue(PREFIX_ADDRESS).isPresent()) { + editStudentDescriptor + .setAddress(ParserUtil.parseAddress(argMultimapBeforeNok.getValue(PREFIX_ADDRESS).get())); + } + + parseTagsForEdit(argMultimapBeforeNok.getAllValues(PREFIX_TAG)).ifPresent(editStudentDescriptor::setTags); + + // For after nok + if (argMultimapAfterNok.getValue(PREFIX_NAME).isPresent()) { + editStudentDescriptor.setNokName(ParserUtil.parseName(argMultimapAfterNok.getValue(PREFIX_NAME).get())); + } + if (argMultimapAfterNok.getValue(PREFIX_PHONE).isPresent()) { + editStudentDescriptor.setNokPhone(ParserUtil.parsePhone(argMultimapAfterNok.getValue(PREFIX_PHONE).get())); + } + if (argMultimapAfterNok.getValue(PREFIX_EMAIL).isPresent()) { + editStudentDescriptor.setNokEmail(ParserUtil.parseEmail(argMultimapAfterNok.getValue(PREFIX_EMAIL).get())); + } + if (argMultimapAfterNok.getValue(PREFIX_ADDRESS).isPresent()) { + editStudentDescriptor + .setNokAddress(ParserUtil.parseAddress(argMultimapAfterNok.getValue(PREFIX_ADDRESS).get())); + } + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editStudentDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + checkTagsAreValid(tags); + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } + +} diff --git a/src/main/java/seedu/times/logic/parser/FindNameCommandParser.java b/src/main/java/seedu/times/logic/parser/FindNameCommandParser.java new file mode 100644 index 00000000000..b9e1962f960 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/FindNameCommandParser.java @@ -0,0 +1,32 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.ParserUtil.FIND_REGEX_WITH_COMMA_DELIMITER; +import static seedu.times.logic.parser.ParserUtil.getSearchTermList; + +import seedu.times.logic.commands.FindNameCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindNameCommand object + */ +public class FindNameCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindNameCommand + * and returns a FindNameCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public FindNameCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + + return new FindNameCommand(new NameContainsKeywordsPredicate(getSearchTermList(trimmedArgs, + FIND_REGEX_WITH_COMMA_DELIMITER))); + } +} diff --git a/src/main/java/seedu/times/logic/parser/FindTagCommandParser.java b/src/main/java/seedu/times/logic/parser/FindTagCommandParser.java new file mode 100644 index 00000000000..75ba4915fcb --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/FindTagCommandParser.java @@ -0,0 +1,32 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.ParserUtil.FIND_REGEX_WITH_COMMA_DELIMITER; +import static seedu.times.logic.parser.ParserUtil.getSearchTermList; + +import seedu.times.logic.commands.FindTagCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.predicates.TagsContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new {@code FindTagCommand} object + */ +public class FindTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindTagCommand + * and returns a FindTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public FindTagCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTagCommand.MESSAGE_USAGE)); + } + + return new FindTagCommand(new TagsContainsKeywordsPredicate(getSearchTermList(trimmedArgs, + FIND_REGEX_WITH_COMMA_DELIMITER))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/times/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/times/logic/parser/Parser.java index d6551ad8e3f..8aa8d5ebf5f 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/times/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.times.logic.commands.Command; +import seedu.times.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/seedu/times/logic/parser/ParserUtil.java b/src/main/java/seedu/times/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..2703f7ee284 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/ParserUtil.java @@ -0,0 +1,252 @@ +package seedu.times.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.times.model.tag.Tag.MAX_TAG_LENGTH; +import static seedu.times.model.tag.Tag.MAX_TAG_NUMBER; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_LONG; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_MANY; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.commons.util.StringUtil; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; +import seedu.times.model.tag.Tag; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; + +/** + * 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."; + public static final String FIND_REGEX_WITH_COMMA_DELIMITER = ",\\s*"; + + /** + * 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)); + } + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code String phone} into a {@code Phone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code phone} 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); + } + return new Phone(trimmedPhone); + } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static Address parseAddress(String address) throws ParseException { + requireNonNull(address); + String trimmedAddress = address.trim(); + if (!Address.isValidAddress(trimmedAddress)) { + throw new ParseException(Address.MESSAGE_CONSTRAINTS); + } + return new Address(trimmedAddress); + } + + /** + * Parses a {@code String classTiming} into an {@code ClassTiming}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code classTiming} is invalid. + */ + public static ClassTiming parseClassTiming(String classTiming) throws ParseException { + requireNonNull(classTiming); + String trimmedClassTiming = classTiming.trim(); + if (!ClassTiming.isValidClassTiming(trimmedClassTiming)) { + throw new ParseException(ClassTiming.MESSAGE_CONSTRAINTS); + } + return new ClassTiming(trimmedClassTiming); + } + + /** + * Parses a {@code String location} into an {@code Location}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code location} is invalid. + */ + public static Location parseLocation(String location) throws ParseException { + requireNonNull(location); + String trimmedAddress = location.trim(); + if (!Location.isValidLocation(trimmedAddress)) { + throw new ParseException(Location.MESSAGE_CONSTRAINTS); + } + return new Location(trimmedAddress); + } + + /** + * Parses a {@code String nok} into an {@code Nok}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code nok} is invalid. + */ + public static Address parseNok(String nok) throws ParseException { + requireNonNull(nok); + String trimmedAddress = nok.trim(); + if (!Address.isValidAddress(trimmedAddress)) { + throw new ParseException(Address.MESSAGE_CONSTRAINTS); + } + return new Address(trimmedAddress); + } + + /** + * Parses a {@code String email} into an {@code Email}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code email} 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); + } + return new Email(trimmedEmail); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tag} 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); + } + return new Tag(trimmedTag); + } + + /** + * Parses a {@code String rate} into a {@code Rate}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code rate} is invalid. + */ + public static Rate parseRate(String rate) throws ParseException { + requireNonNull(rate); + String trimmedRate = rate.trim(); + if (!Rate.isValidRate(trimmedRate)) { + throw new ParseException(Rate.MESSAGE_CONSTRAINTS); + } + return new Rate(trimmedRate); + } + + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseTags(Collection tags) throws ParseException { + requireNonNull(tags); + checkTagsAreValid(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + /** + * Checks whether the tags are within the constraints. + * + * @param tags The tags to check. + * @throws ParseException if the given tags are invalid. + */ + public static void checkTagsAreValid(Collection tags) throws ParseException { + if (tags.size() > MAX_TAG_NUMBER) { + throw new ParseException(MESSAGE_CONSTRAINTS_TOO_MANY); + } + for (String tagName : tags) { + if (tagName.length() > MAX_TAG_LENGTH) { + throw new ParseException(MESSAGE_CONSTRAINTS_TOO_LONG); + } + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses a {@code String className} into a {@code ClassName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code className} is invalid. + */ + public static ClassName parseClassName(String className) throws ParseException { + requireNonNull(className); + String trimmedClassName = className.trim(); + if (!ClassName.isValidClassName(trimmedClassName)) { + throw new ParseException(ClassName.MESSAGE_CONSTRAINTS); + } + return new ClassName(trimmedClassName); + } + + /** + * Returns a list of search terms to be used for filtering student or class list. + * + * @throws ParseException if given {@code trimmedArgs} does not have any search term + */ + public static List getSearchTermList(String trimmedArgs, String regex) throws ParseException { + String[] searchTerms = trimmedArgs.split(regex); + if (searchTerms.length == 0) { + throw new ParseException(Messages.MESSAGE_NO_SEARCH_TERMS_ENTERED); + } + return Arrays.asList(searchTerms); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/times/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/times/logic/parser/Prefix.java index c859d5fa5db..6277421317f 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/times/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/seedu/times/logic/parser/SortCommandParser.java b/src/main/java/seedu/times/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..442d3ea71f7 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/SortCommandParser.java @@ -0,0 +1,80 @@ +package seedu.times.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.times.logic.commands.SortCommand; +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + + private final String[] validSortByKeywords = new String[]{"name", "timing"}; + private final String[] validDirectionOfSortKeywords = new String[]{"asc", "desc"}; + + /** + * Parses the given {@code String} of arguments in the context of the SortCommand. + * and returns an SortCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public SortCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String[] splitArgs = trimmedArgs.split(" "); + if (splitArgs.length != 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String sortBy = splitArgs[0]; + checkSortBy(sortBy); + + // sort by asc or desc + String directionOfSort = splitArgs[1]; + checkDirectionOfSort(directionOfSort); + + return new SortCommand(sortBy, directionOfSort); + } + + /** + * Checks the string to see if it is a valid sortBy keyword. + * + * @param sortBy String to check. + * @throws ParseException If it is not a valid sortBy keyword. + */ + public void checkSortBy(String sortBy) throws ParseException { + requireNonNull(sortBy); + for (String validSortByKeyword : validSortByKeywords) { + if (sortBy.equals(validSortByKeyword)) { + return; + } + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_SORTBY)); + } + + /** + * Checks the string to see if its a valid direction of sort keyword. + * + * @param directionOfSort String to check. + * @throws ParseException If it is not a valid directionOfSort keyword. + */ + public void checkDirectionOfSort(String directionOfSort) throws ParseException { + requireNonNull(directionOfSort); + for (String validDirectionOfSortKeyword : validDirectionOfSortKeywords) { + if (directionOfSort.equals(validDirectionOfSortKeyword)) { + return; + } + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_DIRECTIONOFSORT)); + } +} diff --git a/src/main/java/seedu/times/logic/parser/TimesTableParser.java b/src/main/java/seedu/times/logic/parser/TimesTableParser.java new file mode 100644 index 00000000000..2bd2264c571 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/TimesTableParser.java @@ -0,0 +1,137 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.commands.ClearCommand; +import seedu.times.logic.commands.Command; +import seedu.times.logic.commands.DeleteCommand; +import seedu.times.logic.commands.EditCommand; +import seedu.times.logic.commands.ExitCommand; +import seedu.times.logic.commands.FindNameCommand; +import seedu.times.logic.commands.FindTagCommand; +import seedu.times.logic.commands.HelpCommand; +import seedu.times.logic.commands.ListCommand; +import seedu.times.logic.commands.SortCommand; +import seedu.times.logic.commands.ViewCommand; +import seedu.times.logic.commands.classcommands.AddClassCommand; +import seedu.times.logic.commands.classcommands.AddToClassCommand; +import seedu.times.logic.commands.classcommands.DeleteClassCommand; +import seedu.times.logic.commands.classcommands.EditClassCommand; +import seedu.times.logic.commands.classcommands.FindClassCommand; +import seedu.times.logic.commands.classcommands.FindClassNameCommand; +import seedu.times.logic.commands.classcommands.ListClassCommand; +import seedu.times.logic.commands.classcommands.RemoveFromClassCommand; +import seedu.times.logic.commands.classcommands.SelectClassCommand; +import seedu.times.logic.parser.classcommandparsers.AddClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.AddToClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.DeleteClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.EditClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.FindClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.FindClassNameCommandParser; +import seedu.times.logic.parser.classcommandparsers.RemoveFromClassCommandParser; +import seedu.times.logic.parser.classcommandparsers.SelectClassCommandParser; +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class TimesTableParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + //// student commands + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case FindNameCommand.COMMAND_WORD: + return new FindNameCommandParser().parse(arguments); + + case FindTagCommand.COMMAND_WORD: + return new FindTagCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + //// tuition class commands + + case ListClassCommand.COMMAND_WORD: + return new ListClassCommand(); + + case AddClassCommand.COMMAND_WORD: + return new AddClassCommandParser().parse(arguments); + + case AddToClassCommand.COMMAND_WORD: + return new AddToClassCommandParser().parse(arguments); + + case RemoveFromClassCommand.COMMAND_WORD: + return new RemoveFromClassCommandParser().parse(arguments); + + case EditClassCommand.COMMAND_WORD: + return new EditClassCommandParser().parse(arguments); + + case SelectClassCommand.COMMAND_WORD: + return new SelectClassCommandParser().parse(arguments); + + case DeleteClassCommand.COMMAND_WORD: + return new DeleteClassCommandParser().parse(arguments); + + case FindClassCommand.COMMAND_WORD: + return new FindClassCommandParser().parse(arguments); + + case FindClassNameCommand.COMMAND_WORD: + return new FindClassNameCommandParser().parse(arguments); + + //// general application command + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/times/logic/parser/ViewCommandParser.java b/src/main/java/seedu/times/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..2c3a97300d0 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/ViewCommandParser.java @@ -0,0 +1,54 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.times.logic.commands.ViewCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.ui.TabName; + +/** + * Parses input command and returns ViewCommand. + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns an ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ViewCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + String[] splitArgs = trimmedArgs.split(" "); + if (splitArgs.length > 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + String tabToView = splitArgs[0]; + TabName tab; + + switch (tabToView) { + case "students": + tab = TabName.STUDENTS; + break; + case "classes": + tab = TabName.CLASSES; + break; + case "timetable": + tab = TabName.TIMETABLE; + break; + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.INVALID_TAB)); + } + + return new ViewCommand(tab); + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/AddClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/AddClassCommandParser.java new file mode 100644 index 00000000000..c0748943055 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/AddClassCommandParser.java @@ -0,0 +1,45 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASSTIMING; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASS_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.times.logic.parser.CliSyntax.PREFIX_RATE; +import static seedu.times.logic.parser.ParserUtil.arePrefixesPresent; + +import seedu.times.logic.commands.classcommands.AddClassCommand; +import seedu.times.logic.parser.ArgumentMultimap; +import seedu.times.logic.parser.ArgumentTokenizer; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Parses input arguments and creates a new AddClassCommand object + */ +public class AddClassCommandParser implements Parser { + @Override + public AddClassCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_CLASS_NAME, PREFIX_RATE, PREFIX_CLASSTIMING, PREFIX_LOCATION); + + if (!arePrefixesPresent(argMultimap, PREFIX_CLASS_NAME, PREFIX_RATE, PREFIX_CLASSTIMING, PREFIX_LOCATION) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClassCommand.MESSAGE_USAGE)); + } + + ClassName className = ParserUtil.parseClassName(argMultimap.getValue(PREFIX_CLASS_NAME).get()); + Rate rate = ParserUtil.parseRate(argMultimap.getValue(PREFIX_RATE).get()); + ClassTiming classTiming = ParserUtil.parseClassTiming(argMultimap.getValue(PREFIX_CLASSTIMING).get()); + Location location = ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get()); + + return new AddClassCommand(new TuitionClass(className, classTiming, location, rate, new StudentNameList())); + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/AddToClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/AddToClassCommandParser.java new file mode 100644 index 00000000000..0c008f4789e --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/AddToClassCommandParser.java @@ -0,0 +1,69 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; +import java.util.List; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.AddToClassCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new AddToClassCommand object + */ +public class AddToClassCommandParser implements Parser { + + + @Override + public AddToClassCommand parse(String args) throws ParseException { + requireNonNull(args); + + String[] argsArray = args.trim().split("\\s+"); + List indexArray = mapToIndexArray(argsArray); + + return new AddToClassCommand(indexArray); + } + + private List mapToIndexArray(String[] str) throws ParseException { + ArrayList indexArray = new ArrayList<>(); + for (int i = 0; i < str.length; i++) { + try { + Index currentIndex = ParserUtil.parseIndex(str[i]); + indexArray.add(currentIndex); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE), pe); + } + } + + if (indexArray.size() < 2) { + throw new ParseException(AddToClassCommand.NO_STUDENT_INDEX_PROVIDED_MESSAGE); + } + + return copyWithoutDuplicateExcludeFirst(indexArray); + } + + private List copyWithoutDuplicateExcludeFirst(List indexArray) { + int size = indexArray.size(); + assert size >= 2 : "Should have thrown ParseException."; + + List res = new ArrayList<>(); + + for (int i = 1; i < size; i++) { + Index currentIndex = indexArray.get(i); + if (!res.contains(currentIndex)) { + res.add(currentIndex); + } + } + + res.add(0, indexArray.get(0)); + return res; + } + + +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/DeleteClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/DeleteClassCommandParser.java new file mode 100644 index 00000000000..74403e56d65 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/DeleteClassCommandParser.java @@ -0,0 +1,27 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.DeleteClassCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new DeleteClassCommand object + */ +public class DeleteClassCommandParser implements Parser { + + @Override + public DeleteClassCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteClassCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/EditClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/EditClassCommandParser.java new file mode 100644 index 00000000000..00e39ad7d4c --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/EditClassCommandParser.java @@ -0,0 +1,62 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASSTIMING; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASS_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.times.logic.parser.CliSyntax.PREFIX_RATE; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.EditClassCommand; +import seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import seedu.times.logic.parser.ArgumentMultimap; +import seedu.times.logic.parser.ArgumentTokenizer; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditClassCommand object + */ +public class EditClassCommandParser implements Parser { + @Override + public EditClassCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_CLASS_NAME, PREFIX_CLASSTIMING, PREFIX_RATE, PREFIX_LOCATION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + } + + EditClassDescriptor editClassDescriptor = new EditClassDescriptor(); + + if (argMultimap.getValue(PREFIX_CLASS_NAME).isPresent()) { + editClassDescriptor.setClassName(ParserUtil.parseClassName(argMultimap.getValue(PREFIX_CLASS_NAME).get())); + } + if (argMultimap.getValue(PREFIX_CLASSTIMING).isPresent()) { + editClassDescriptor.setClassTiming(ParserUtil + .parseClassTiming(argMultimap.getValue(PREFIX_CLASSTIMING).get())); + } + if (argMultimap.getValue(PREFIX_RATE).isPresent()) { + editClassDescriptor.setRate(ParserUtil.parseRate(argMultimap.getValue(PREFIX_RATE).get())); + } + if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) { + editClassDescriptor.setLocation(ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get())); + } + + if (!editClassDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditClassCommand.MESSAGE_NO_FIELD_PROVIDED); + } + + return new EditClassCommand(index, editClassDescriptor); + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassCommandParser.java new file mode 100644 index 00000000000..29ce0a71276 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassCommandParser.java @@ -0,0 +1,50 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.times.logic.commands.classcommands.FindClassCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.tuitionclass.predicates.ClassTimingContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindClassCommand object + */ +public class FindClassCommandParser implements Parser { + + private static final String VALIDATION_REGEX_DAY = "(?i)(MON|TUE|WED|THU|FRI|SAT|SUN){1}"; + private static final String VALIDATION_REGEX_TIME = "([01][0-9]|2[0-3]):[03]0-(([01][0-9]|2[0-3])" + + ":[03]0|23:59){1}"; + private static final String FIND_REGEX = "\\s+"; + + /** + * Returns true if all keywords contained in {@code classTimingKeywords} is a three-letter abbreviation + * for day of the week or in a valid time format, HH:MM-HH:MM. + * + * @param classTimingKeywords Array of string keywords. + */ + private static boolean isAllValidKeyword(String[] classTimingKeywords) { + return Arrays.stream(classTimingKeywords).allMatch(keyword -> { + return keyword.matches(VALIDATION_REGEX_TIME) || keyword.matches(VALIDATION_REGEX_DAY); + }); + } + + @Override + public FindClassCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + String[] classTimingKeywords = trimmedArgs.split(FIND_REGEX); + + if (!isAllValidKeyword(classTimingKeywords)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + return new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList(classTimingKeywords))); + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassNameCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassNameCommandParser.java new file mode 100644 index 00000000000..471a1a8c66f --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/FindClassNameCommandParser.java @@ -0,0 +1,28 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.ParserUtil.FIND_REGEX_WITH_COMMA_DELIMITER; +import static seedu.times.logic.parser.ParserUtil.getSearchTermList; + +import seedu.times.logic.commands.classcommands.FindClassNameCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.tuitionclass.predicates.ClassNameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindClassNameCommand object + */ +public class FindClassNameCommandParser implements Parser { + + @Override + public FindClassNameCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassNameCommand.MESSAGE_USAGE)); + } + + return new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(getSearchTermList(trimmedArgs, + FIND_REGEX_WITH_COMMA_DELIMITER))); + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/RemoveFromClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/RemoveFromClassCommandParser.java new file mode 100644 index 00000000000..2c9cc73a432 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/RemoveFromClassCommandParser.java @@ -0,0 +1,50 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; +import static seedu.times.logic.commands.classcommands.AddToClassCommand.CLASS_INDEX_POSITION; + +import java.util.ArrayList; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.AddToClassCommand; +import seedu.times.logic.commands.classcommands.RemoveFromClassCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new RemoveFromClassCommand object + */ +public class RemoveFromClassCommandParser implements Parser { + + @Override + public RemoveFromClassCommand parse(String args) throws ParseException { + requireNonNull(args); + + String[] argsArray = args.trim().split("\\s+"); + ArrayList indexArray = mapToIndex(argsArray); + + return new RemoveFromClassCommand(indexArray); + } + + private ArrayList mapToIndex(String[] str) throws ParseException { + ArrayList indexArray = new ArrayList<>(); + for (int i = 0; i < str.length; i++) { + try { + indexArray.add(ParserUtil.parseIndex(str[i])); + } catch (ParseException pe) { + String errorMessage = i == CLASS_INDEX_POSITION + ? AddToClassCommand.INVALID_OR_MISSING_CLASS_INDEX + : MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE), pe); + } + } + if (indexArray.size() < 2) { + throw new ParseException(RemoveFromClassCommand.NO_STUDENT_INDEX_PROVIDED_MESSAGE); + } + return indexArray; + } +} diff --git a/src/main/java/seedu/times/logic/parser/classcommandparsers/SelectClassCommandParser.java b/src/main/java/seedu/times/logic/parser/classcommandparsers/SelectClassCommandParser.java new file mode 100644 index 00000000000..81554316413 --- /dev/null +++ b/src/main/java/seedu/times/logic/parser/classcommandparsers/SelectClassCommandParser.java @@ -0,0 +1,43 @@ +package seedu.times.logic.parser.classcommandparsers; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.EditClassCommand; +import seedu.times.logic.commands.classcommands.SelectClassCommand; +import seedu.times.logic.parser.Parser; +import seedu.times.logic.parser.ParserUtil; +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectClassCommand object + */ +public class SelectClassCommandParser implements Parser { + + @Override + public SelectClassCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectClassCommand.MESSAGE_USAGE)); + } + + String[] splitArgs = trimmedArgs.split(" "); + if (splitArgs.length > 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectClassCommand.MESSAGE_USAGE)); + } + + Index indexOfClassToView; + + try { + indexOfClassToView = ParserUtil.parseIndex(splitArgs[0]); + } catch (ParseException | ArrayIndexOutOfBoundsException err) { + throw new ParseException(String.format(MESSAGE_INVALID_CLASS_DISPLAYED_INDEX, + EditClassCommand.MESSAGE_USAGE)); + } + + return new SelectClassCommand(indexOfClassToView); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/times/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/times/logic/parser/exceptions/ParseException.java index 158a1a54c1c..e1f38df166e 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/times/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.times.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.times.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/times/model/Model.java b/src/main/java/seedu/times/model/Model.java new file mode 100644 index 00000000000..c1dcde2cf25 --- /dev/null +++ b/src/main/java/seedu/times/model/Model.java @@ -0,0 +1,148 @@ +package seedu.times.model; + +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.times.commons.core.GuiSettings; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * The API of the Model component. + */ +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_CLASS = unused -> true; + + + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' address book file path. + */ + Path getTimesTableFilePath(); + + /** + * Sets the user prefs' address book file path. + */ + void setTimesTableFilePath(Path timesTableFilePath); + + /** + * Replaces address book data with the data in {@code timestable}. + */ + void setTimesTable(ReadOnlyTimesTable timesTable); + + /** Returns the TimesTable */ + ReadOnlyTimesTable getTimesTable(); + + /** + * Returns true if a student with the same identity as {@code student} exists in the TimesTable. + */ + boolean hasStudent(Student student); + + /** + * Deletes the given student. + * The person must exist in the TimesTable. + */ + void deleteStudent(Student target); + + /** + * Adds the given person. + * {@code person} must not already exist in the TimesTable. + */ + void addStudent(Student student); + + /** + * Returns true if a class with the same identity as {@code TuitionClass} exists in the TimesTable. + */ + boolean hasTuitionClass(TuitionClass tuitionClass); + + /** + * Adds the given tuition class. + * {@code TuitionClass} must not already exist in the address book. + */ + void addTuitionClass(TuitionClass tuitionClass); + + /** + * Deletes the given tuition class. + * The class must exist in the TimesTable. + */ + void deleteTuitionClass(TuitionClass target); + + /** + * Replaces the given student {@code target} with {@code editedStudent}. + * {@code target} must exist in the TimesTable. + * The person identity of {@code editedPerson} must not be the same as another existing person in the TimesTable. + */ + void setStudent(Student target, Student editedStudent); + + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); + + /** + * Updates the filter of the filtered student list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredStudentList(Predicate predicate); + + void updateFilteredClassList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered tuition class list */ + ObservableList getFilteredTuitionClassList(); + + /** + * Replaces the given class {@code target} with {@code editedPerson}. + * {@code target} must exist in the address book. + * The Class identity of {@code editedClass} must not be the same as another existing class in the address book. + */ + void setClass(TuitionClass target, TuitionClass editedClass); + + /** + * Replaces the filteredTuitionClass with the classes. + */ + void setClasses(List classes); + + /** + * Replaces the filtered student list with the classes. + */ + void setStudents(List students); + + /** Executes update cascade after change of student name for {@code StudentNameList}. */ + void updateClassStudentLists(Name newName, Name oldName); + + /** + * Sorts the {@code Student} according to comparator c. + */ + void sortStudents(Comparator c); + + /** + * Sorts the {@code TuitionClass} according to comparator c. + */ + void sortClasses(Comparator c); +} diff --git a/src/main/java/seedu/times/model/ModelManager.java b/src/main/java/seedu/times/model/ModelManager.java new file mode 100644 index 00000000000..9f8ad1b6b53 --- /dev/null +++ b/src/main/java/seedu/times/model/ModelManager.java @@ -0,0 +1,231 @@ +package seedu.times.model; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.core.LogsCenter; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Represents the in-memory model of the timestable data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final TimesTable timesTable; + private final UserPrefs userPrefs; + private FilteredList filteredStudents; + private FilteredList filteredTuitionClass; + + + /** + * Initializes a ModelManager with the given timestable and userPrefs. + */ + public ModelManager(ReadOnlyTimesTable timestable, ReadOnlyUserPrefs userPrefs) { + super(); + requireAllNonNull(timestable, userPrefs); + + logger.fine("Initializing with timestable: " + timestable + " and user prefs " + userPrefs); + + this.timesTable = new TimesTable(timestable); + this.userPrefs = new UserPrefs(userPrefs); + filteredStudents = new FilteredList<>(this.timesTable.getStudentList()); + filteredTuitionClass = + new FilteredList<>(this.timesTable.getTuitionClassList()); + } + + public ModelManager() { + this(new TimesTable(), new UserPrefs()); + } + + //=========== UserPrefs ================================================================================== + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + @Override + public Path getTimesTableFilePath() { + return userPrefs.getTimesTableFilePath(); + } + + @Override + public void setTimesTableFilePath(Path timesTableFilePath) { + requireNonNull(timesTableFilePath); + userPrefs.setTimesTableFilePath(timesTableFilePath); + } + + //=========== TimesTable ================================================================================ + + @Override + public ReadOnlyTimesTable getTimesTable() { + return timesTable; + } + + @Override + public void setTimesTable(ReadOnlyTimesTable timestable) { + this.timesTable.resetData(timestable); + } + + @Override + public boolean hasStudent(Student student) { + requireNonNull(student); + return timesTable.hasPerson(student); + } + + @Override + public boolean hasTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + return timesTable.hasTuitionClass(tuitionClass); + } + + @Override + public void deleteStudent(Student target) { + timesTable.removePerson(target); + } + + @Override + public void addStudent(Student student) { + timesTable.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + timesTable.addTuitionClass(tuitionClass); + updateFilteredClassList(PREDICATE_SHOW_ALL_CLASS); + } + + @Override + public void deleteTuitionClass(TuitionClass tuitionClass) { + timesTable.deleteTuitionClass(tuitionClass); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + timesTable.setPerson(target, editedStudent); + } + + //=========== Filtered Person List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedTimesTable} + */ + @Override + public ObservableList getFilteredStudentList() { + return filteredStudents; + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + requireNonNull(predicate); + filteredStudents.setPredicate(predicate); + } + + @Override + public void updateFilteredClassList(Predicate predicate) { + requireNonNull(predicate); + filteredTuitionClass.setPredicate(predicate); + } + + @Override + public void updateClassStudentLists(Name newName, Name oldName) { + requireAllNonNull(newName, oldName); + if (newName.equals(oldName)) { + return; + } + timesTable.updateClassStudentLists(newName, oldName); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + return filteredTuitionClass; + } + + @Override + public void setClass(TuitionClass target, TuitionClass editedClass) { + requireAllNonNull(target, editedClass); + timesTable.setClass(target, editedClass); + updateFilteredClassList(PREDICATE_SHOW_ALL_CLASS); + } + + @Override + public void setClasses(List classes) { + timesTable.setClasses(classes); + } + + @Override + public void setStudents(List studentsList) { + timesTable.setStudents(studentsList); + } + + @Override + public void sortStudents(Comparator c) { + updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + ArrayList studentsToSort = new ArrayList<>(getFilteredStudentList()); + studentsToSort.sort(c); + setStudents(studentsToSort); + } + + @Override + public void sortClasses(Comparator c) { + updateFilteredClassList(PREDICATE_SHOW_ALL_CLASS); + ArrayList classesToSort = new ArrayList<>(getFilteredTuitionClassList()); + classesToSort.sort(c); + setClasses(classesToSort); + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof ModelManager)) { + return false; + } + + // state check + ModelManager other = (ModelManager) obj; + return timesTable.equals(other.timesTable) + && userPrefs.equals(other.userPrefs) + && filteredStudents.equals(other.filteredStudents) + && filteredTuitionClass.equals(other.filteredTuitionClass); + } + +} diff --git a/src/main/java/seedu/times/model/ReadOnlyTimesTable.java b/src/main/java/seedu/times/model/ReadOnlyTimesTable.java new file mode 100644 index 00000000000..71929b0cebe --- /dev/null +++ b/src/main/java/seedu/times/model/ReadOnlyTimesTable.java @@ -0,0 +1,25 @@ +package seedu.times.model; + +import javafx.collections.ObservableList; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyTimesTable { + + /** + * Returns an unmodifiable view of the persons list. + * This list will not contain any duplicate persons. + */ + ObservableList getStudentList(); + + /** + * Returns an unmodifiable view of the class list. + * This list will not contain any duplicate classes. + * @return + */ + ObservableList getTuitionClassList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/times/model/ReadOnlyUserPrefs.java similarity index 57% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/seedu/times/model/ReadOnlyUserPrefs.java index befd58a4c73..67ebef39511 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/times/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package seedu.times.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import seedu.times.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getTimesTableFilePath(); } diff --git a/src/main/java/seedu/times/model/TimesTable.java b/src/main/java/seedu/times/model/TimesTable.java new file mode 100644 index 00000000000..fcb8fed32f0 --- /dev/null +++ b/src/main/java/seedu/times/model/TimesTable.java @@ -0,0 +1,173 @@ +package seedu.times.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.person.UniqueStudentList; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.UniqueClassList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class TimesTable implements ReadOnlyTimesTable { + + private final UniqueStudentList students; + private final UniqueClassList classes; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + students = new UniqueStudentList(); + classes = new UniqueClassList(); + } + + public TimesTable() {} + + /** + * Creates a TimesTable using the Persons in the {@code toBeCopied} + */ + public TimesTable(ReadOnlyTimesTable toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the person list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setStudents(List students) { + this.students.setPersons(students); + } + + public void setClasses(List classes) { + this.classes.setClasses(classes); + } + + /** + * Resets the existing data of this {@code TimesTable} with {@code newData}. + */ + public void resetData(ReadOnlyTimesTable newData) { + requireNonNull(newData); + + setStudents(newData.getStudentList()); + setClasses(newData.getTuitionClassList()); + } + + //// person-level operations + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + public boolean hasPerson(Student student) { + requireNonNull(student); + return students.contains(student); + } + + /** + * Returns true if a tuition class with the same identity as {@code TuitionClass} exists in the address book. + */ + public boolean hasTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + return classes.contains(tuitionClass); + } + + /** + * Adds a person to the address book. + * The person must not already exist in the address book. + */ + public void addStudent(Student p) { + students.add(p); + } + + /** + * Adds a tuition class to the address book. + * The tuition class must not already exist in the address book. + */ + public void addTuitionClass(TuitionClass t) { + classes.add(t); + } + + /** + * Replaces the given person {@code target} in the list 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. + */ + public void setPerson(Student target, Student editedStudent) { + requireNonNull(editedStudent); + + students.setPerson(target, editedStudent); + } + + /** + * Removes {@code key} from this {@code TimesTable}. + * {@code key} must exist in the address book. + */ + public void removePerson(Student key) { + students.remove(key); + classes.removeStudent(key.getName()); + } + + public void deleteTuitionClass(TuitionClass toDelete) { + classes.delete(toDelete); + } + + //// util methods + + @Override + public String toString() { + return students.asUnmodifiableObservableList().size() + " persons"; + // TODO: refine later + } + + @Override + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getTuitionClassList() { + return classes.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TimesTable // instanceof handles nulls + && students.equals(((TimesTable) other).students)); + } + + @Override + public int hashCode() { + return students.hashCode(); + } + + public void setClass(TuitionClass target, TuitionClass editedClass) { + requireNonNull(editedClass); + classes.setClass(target, editedClass); + } + + /** + * Replaces the given old name in the list with a new name. + * + * @param newName New name to replace the given old name. + * @param oldName Old name to be replaced by new name. + */ + public void updateClassStudentLists(Name newName, Name oldName) { + for (TuitionClass tuitionClass : classes) { + tuitionClass.replaceStudentName(newName, oldName); + } + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/times/model/UserPrefs.java similarity index 70% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/times/model/UserPrefs.java index 25a5fd6eab9..b29fa02782f 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/times/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package seedu.times.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import seedu.times.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path timesTableFilePath = Paths.get("data" , "timestable.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setTimesTableFilePath(newUserPrefs.getTimesTableFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getTimesTableFilePath() { + return timesTableFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setTimesTableFilePath(Path timesTableFilePath) { + requireNonNull(timesTableFilePath); + this.timesTableFilePath = timesTableFilePath; } @Override @@ -68,19 +68,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && timesTableFilePath.equals(o.timesTableFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, timesTableFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + timesTableFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/times/model/person/Address.java similarity index 71% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/times/model/person/Address.java index 60472ca22a0..ab70cbffdb4 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/times/model/person/Address.java @@ -1,21 +1,23 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.model.util.Commons.MAX_CHAR_COUNT; /** - * Represents a Person's address in the address book. + * Represents a Person's address in the Timestable. * 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"; + public static final String MESSAGE_CONSTRAINTS = "Address is blank or has exceeded " + + MAX_CHAR_COUNT + " 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 = "[^\\s].*"; + public static final String VALIDATION_REGEX = "[^\\s].{0,84}"; public final String value; @@ -31,7 +33,7 @@ public Address(String address) { } /** - * Returns true if a given string is a valid email. + * Returns true if a given string is a valid address. */ public static boolean isValidAddress(String test) { return test.matches(VALIDATION_REGEX); diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/times/model/person/Email.java similarity index 82% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/times/model/person/Email.java index f866e7133de..1765a0c78f3 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/times/model/person/Email.java @@ -1,10 +1,12 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.isWithinLength; +import static seedu.times.model.util.Commons.MAX_CHAR_COUNT; /** - * Represents a Person's email in the address book. + * Represents a Person's email in the Timestable. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -12,10 +14,11 @@ public class Email { private static final String SPECIAL_CHARACTERS = "+_.-"; public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " + + "1. The total length of the email must not exceed 85 characters\n" + + "2. 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 " + + "3. 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" @@ -48,7 +51,7 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && isWithinLength(test, MAX_CHAR_COUNT); } @Override diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/times/model/person/Name.java similarity index 55% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/times/model/person/Name.java index 79244d71cf7..f114dbfcbd2 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/times/model/person/Name.java @@ -1,19 +1,22 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.isWithinLength; /** - * Represents a Person's name in the address book. + * Represents a Person's name in the Timestable. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ -public class Name { +public class Name implements Comparable { + public static final int MAX_CHAR_COUNT = 120; public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names contains characters that is not alphanumeric/space, or the name is not between 1 and 120 " + + "characters long."; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; @@ -31,11 +34,15 @@ public Name(String name) { fullName = name; } + public String getFullName() { + return fullName; + } + /** * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && isWithinLength(test, MAX_CHAR_COUNT); } @@ -48,7 +55,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.compareToIgnoreCase(((Name) other).fullName) == 0); // compares names, ignores case } @Override @@ -56,4 +63,9 @@ public int hashCode() { return fullName.hashCode(); } + @Override + public int compareTo(Name otherName) { + return fullName.compareTo(otherName.fullName); + } + } diff --git a/src/main/java/seedu/times/model/person/Nok.java b/src/main/java/seedu/times/model/person/Nok.java new file mode 100644 index 00000000000..e930489b2bc --- /dev/null +++ b/src/main/java/seedu/times/model/person/Nok.java @@ -0,0 +1,14 @@ +package seedu.times.model.person; + +/** + * Represents a students Next of Kin (ie parent etc.). + */ +public class Nok extends Person { + + /** + * Every field must be present and not null. + */ + public Nok(Name name, Phone phone, Email email, Address address) { + super(name, phone, email, address); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/times/model/person/Person.java similarity index 70% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/seedu/times/model/person/Person.java index 8ff1d83fe89..ce11a6fb110 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/times/model/person/Person.java @@ -1,39 +1,31 @@ -package seedu.address.model.person; +package seedu.times.model.person; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; /** - * Represents a Person in the address book. + * Represents a Person in the Timestable. * Guarantees: details are present and not null, field values are validated, immutable. */ public class Person { - // Identity fields + // Identity (mandatory) fields private final Name name; private final Phone phone; private final Email email; - // Data fields private final Address address; - private final Set tags = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address) { + requireAllNonNull(name, phone, email, address); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); } public Name getName() { @@ -52,14 +44,6 @@ public Address getAddress() { return address; } - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. @@ -91,14 +75,13 @@ public boolean equals(Object other) { return otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getAddress().equals(getAddress()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address); } @Override @@ -112,11 +95,6 @@ public String toString() { .append("; Address: ") .append(getAddress()); - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } return builder.toString(); } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/times/model/person/Phone.java similarity index 68% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/times/model/person/Phone.java index 872c76b382f..2e30ddd1d02 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/times/model/person/Phone.java @@ -1,18 +1,22 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Person's phone number in the Timestable. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { + public static final int MAX_DIGIT_COUNT = 25; + public static final int MIN_DIGIT_COUNT = 3; 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,}"; + "Phone number contains non-number characters or its length is not between " + + MIN_DIGIT_COUNT + " and " + + MAX_DIGIT_COUNT + " digits!"; + public static final String VALIDATION_REGEX = "\\d{3,25}"; public final String value; /** diff --git a/src/main/java/seedu/times/model/person/Student.java b/src/main/java/seedu/times/model/person/Student.java new file mode 100644 index 00000000000..1afd9e8c264 --- /dev/null +++ b/src/main/java/seedu/times/model/person/Student.java @@ -0,0 +1,123 @@ +package seedu.times.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.times.model.tag.Tag; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; + +/** + * Represents a Student in the Timestable. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student extends Person { + + /* Each Student has 0..1 Nok */ + private Nok nok; + + private final Set tags = new HashSet<>(); + + /** + * Student constructor + */ + public Student( + Name name, Phone phone, Email email, Address address, Nok nok, Set tags) { + super(name, phone, email, address); + + this.nok = nok; + this.tags.addAll(tags); + } + + public void setNok(Nok nok) { + this.nok = nok; + } + + public Nok getNok() { + return nok; + } + + public ClassTiming getClassTiming() { + // TODO: Placeholder + return null; + } + + public Location getLocation() { + // TODO: Placeholder + return null; + } + + /** + * Returns tuition {@code Rate} of this student + */ + public Rate getRate() { + // Placeholder + return null; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if any {@code Tag} belonging to this student matches {@code keyword} in name ignoring case. + */ + public boolean isAnyTagsMatching(String keyword) { + requireNonNull(keyword); + return tags.stream() + .anyMatch(tag -> tag.isNameMatchingIgnoreCase(keyword)); + } + + /** + * Returns true if both persons have the same identity and data fields. + * This defines a stronger notion of equality between two persons. + * We check if their tags are equal as well. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student o = (Student) other; + return super.equals(other) + && o.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // Hashes the hashcode to produce a new hash + return Objects.hash(super.hashCode(), tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + + Set tags = getTags(); + if (!tags.isEmpty()) { + builder.append("; Tags: "); + tags.forEach(builder::append); + } + + builder.append("\nNext-of-Kin: "); + builder.append(nok.toString()); + + + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/times/model/person/UniqueStudentList.java b/src/main/java/seedu/times/model/person/UniqueStudentList.java new file mode 100644 index 00000000000..44e80f97f23 --- /dev/null +++ b/src/main/java/seedu/times/model/person/UniqueStudentList.java @@ -0,0 +1,173 @@ +package seedu.times.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.times.commons.core.LogsCenter; +import seedu.times.model.person.exceptions.DuplicatePersonException; +import seedu.times.model.person.exceptions.PersonNotFoundException; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and + * updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is + * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so + * as to ensure that the person with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Person#isSamePerson(Person) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + private final Logger logger = LogsCenter.getLogger(UniqueStudentList.class); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePerson); + } + + /** + * Adds a person to the list. + * The person must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePersonException(); + } + int listSize = internalList.size(); + + /* + * TODO: + * As of the new implementation there is a possibility a Student has multiple classes, so this is commented out + * for now + * for (int i = 0; i < listSize; i++) { + * + * if (internalList.get(i).getClassTiming().isEarlier(toAdd.getClassTiming())) { + * continue; + * } else { + * internalList.add(i, toAdd); + * break; + * } + * } + */ + + if (listSize == 0 || internalList.size() == listSize) { + internalList.add(toAdd); + } + + } + + public Student getStudent(Name name) { + requireAllNonNull(name); + for (Student student : internalList) { + if (student.getName() == name) { + return student; + } + } + return null; + } + + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The person identity of {@code editedPerson} must not be the same as another existing person in the list. + */ + public void setPerson(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSamePerson(editedStudent) && contains(editedStudent)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedStudent); + } + + /** + * Removes the equivalent person from the list. + * The person must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PersonNotFoundException(); + } + } + + public void setPersons(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + logger.info("List of Students set from replace UniquePersonList."); + } + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setPersons(List students) { + requireAllNonNull(students); + if (!personsAreUnique(students)) { + throw new DuplicatePersonException(); + } + + internalList.setAll(students); + logger.info("List of Students set from List."); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code persons} contains only unique persons. + */ + private boolean personsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSamePerson(students.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/times/model/person/exceptions/DuplicatePersonException.java similarity index 87% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/seedu/times/model/person/exceptions/DuplicatePersonException.java index d7290f59442..04205a7a01e 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/times/model/person/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.times.model.person.exceptions; /** * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/times/model/person/exceptions/PersonNotFoundException.java similarity index 75% rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java rename to src/main/java/seedu/times/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..fc32e5502f9 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/times/model/person/exceptions/PersonNotFoundException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.times.model.person.exceptions; /** * Signals that the operation is unable to find the specified person. diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/times/model/person/predicates/NameContainsKeywordsPredicate.java similarity index 71% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/times/model/person/predicates/NameContainsKeywordsPredicate.java index c9b5868427c..2fe5a224fca 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/times/model/person/predicates/NameContainsKeywordsPredicate.java @@ -1,14 +1,14 @@ -package seedu.address.model.person; +package seedu.times.model.person.predicates; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import seedu.times.model.person.Student; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,10 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Student student) { + String nameLowerCase = student.getName().fullName.toLowerCase(); return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> nameLowerCase.contains(keyword.toLowerCase())); } @Override diff --git a/src/main/java/seedu/times/model/person/predicates/TagsContainsKeywordsPredicate.java b/src/main/java/seedu/times/model/person/predicates/TagsContainsKeywordsPredicate.java new file mode 100644 index 00000000000..49b8acd47d2 --- /dev/null +++ b/src/main/java/seedu/times/model/person/predicates/TagsContainsKeywordsPredicate.java @@ -0,0 +1,30 @@ +package seedu.times.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.times.model.person.Student; + +/** + * Tests if a {@code Person}'s list of {@code Tag} matches any of the keywords given. + */ +public class TagsContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagsContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Student student) { + return keywords.stream() + .anyMatch(keyword -> student.isAnyTagsMatching(keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof TagsContainsKeywordsPredicate + && keywords.equals(((TagsContainsKeywordsPredicate) other).keywords)); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/times/model/tag/Tag.java similarity index 61% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/seedu/times/model/tag/Tag.java index b0ea7e7dad7..5118e643982 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/times/model/tag/Tag.java @@ -1,7 +1,7 @@ -package seedu.address.model.tag; +package seedu.times.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. @@ -10,7 +10,13 @@ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final int MAX_TAG_LENGTH = 15; + public static final int MAX_TAG_NUMBER = 5; + public static final String MESSAGE_CONSTRAINTS_TOO_LONG = + "Tags names should be at most " + MAX_TAG_LENGTH + " characters"; + public static final String MESSAGE_CONSTRAINTS_TOO_MANY = + "A student should have at most " + MAX_TAG_NUMBER + " tags"; public final String tagName; @@ -32,6 +38,13 @@ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns true if {@code tagName} of this {@code Tag} matches exactly with {@code keyword} ignoring case. + */ + public boolean isNameMatchingIgnoreCase(String keyword) { + return tagName.toLowerCase().contains(keyword.toLowerCase()); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/times/model/tuitionclass/ClassName.java b/src/main/java/seedu/times/model/tuitionclass/ClassName.java new file mode 100644 index 00000000000..6bf32170877 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/ClassName.java @@ -0,0 +1,54 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.commons.util.AppUtil.isWithinLength; +import static seedu.times.model.util.Commons.MAX_CHAR_COUNT; + +/** + * Represents the name given to a {@code TuitionClass} by a user + * Guarantees: immutable; is valid as declared in {@link #isValidClassName(String)} + */ +public class ClassName { + public static final String MESSAGE_CONSTRAINTS = + "Class name contains characters that is not alphanumeric/space, or the name is not between 1 and " + + MAX_CHAR_COUNT + " characters long!"; + + /* + * The first character of the name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String className; + + /** + * Constructs a {@code ClassName}. + * + * @param className A valid class name. + */ + public ClassName(String className) { + requireNonNull(className); + checkArgument(isValidClassName(className), MESSAGE_CONSTRAINTS); + this.className = className; + } + + /** + * Returns true if a given string is a valid class name. + */ + public static boolean isValidClassName(String test) { + return test.matches(VALIDATION_REGEX) && isWithinLength(test, MAX_CHAR_COUNT); + } + + @Override + public String toString() { + return className; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClassName // instanceof handles nulls + && className.equals(((ClassName) other).className)); // state check + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/ClassTiming.java b/src/main/java/seedu/times/model/tuitionclass/ClassTiming.java new file mode 100644 index 00000000000..431ac4be7cb --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/ClassTiming.java @@ -0,0 +1,294 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.AppUtil.checkArgument; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import seedu.times.logic.parser.exceptions.ParseException; + +/** + * Represents a {@code TuitionClass}'s class timing in the Timestable. + * Guarantees: immutable; is valid as declared in {@link #isValidClassTiming(String)} + */ +public class ClassTiming implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Class Timing must be in the form DAY HH:MM-HH:MM" + + "\nStart time must be earlier than end time, and " + + "start and end time has to start at the hour or half hour mark, but it can end at 23:59"; + + /* + * The string has to be in the form DAY HH:MM-HH:MM eg MON 23:59-01:00 + */ + public static final String VALIDATION_REGEX = + "^(?i)(MON |TUE |WED |THU |FRI |SAT |SUN )([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9]"; + + public final String value; + + private final LocalTime startTime; + + private final LocalTime endTime; + + private final String day; + + /** + * Constructs a {@code ClassTiming}. + * + * @param classTiming A valid class timing. + */ + public ClassTiming(String classTiming) { + requireNonNull(classTiming); + checkArgument(isValidClassTiming(classTiming), MESSAGE_CONSTRAINTS); + value = formatClassTiming(classTiming); + startTime = getStartTimeFromValue(); + endTime = getEndTimeFromValue(); + day = parseDay(); + } + + /** + * Gets the endTime from value. + * + * @return The end time. + */ + private LocalTime getStartTimeFromValue() { + String[] timePart = splitTiming(this.value); + String startTime = timePart[0]; + return LocalTime.parse(startTime, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Gets the startTime from value. + * + * @return The start time. + */ + private LocalTime getEndTimeFromValue() { + String[] timePart = splitTiming(this.value); + String endTime = timePart[1]; + return LocalTime.parse(endTime, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Formats the classTiming day into caps. + * + * @param classTiming classTiming where day is going to be changed to caps. + * @return classTiming with the day in caps. + */ + private String formatClassTiming(String classTiming) { + String day = classTiming.split(" ")[0].toUpperCase(); + String timing = classTiming.split(" ")[1]; + return day + " " + timing; + } + + /** + * Converts Day from the ClassTiming object into the int representation of the day. + * + * @return int representation of the Day of the ClassTiming object + */ + public int getDayToInt() { + // TODO: change day to enum + switch (day.toUpperCase()) { + case "MON": + return 1; + case "TUE": + return 2; + case "WED": + return 3; + case "THU": + return 4; + case "FRI": + return 5; + case "SAT": + return 6; + case "SUN": + return 7; + default: + return 0; + } + } + + /** + * Compares two ClassTiming objects. + * + * @param time ClassTiming being compared to. + * @return true if this ClassTiming is on an earlier day or has end time earlier than time's + * start time, otherwise false. + */ + public boolean isEarlier(ClassTiming time) { + return this.compareTo(time) < 0; + } + + /** + * Sees if the ClassTiming's start time is after the LocalTime given. + * + * @param time LocalTime being compared to. + * @return true if ClassTiming has a start time later than given time otherwise false. + */ + public boolean isAfter(LocalTime time) { + return this.getStartTime().isAfter(time); + } + + /** + * Parses the class timing string to retrieve the day from the value. + * + * @return The Day of the ClassTiming value. + */ + private String parseDay() { + String[] classTimingPart = this.value.split(" "); + return classTimingPart[0]; + } + + /** + * Splits the class timing string to retrieve the start and end time in String form. + * + * @param ct ClassTiming string to be split. + * @return A String array consisting of the start time at index 0 and end time at index 1. + */ + private static String[] splitTiming(String ct) { + String[] ctSplit = ct.split(" "); + String startEndTime = ctSplit[1]; + return startEndTime.split("-"); + } + + /** + * Parses the input class timing string to retrieve the Start Time. + * + * @param ct ClassTiming string to be parsed. + * @return The start time of the input string. + * @throws ParseException if ct does not start at the hour or half hour mark. + */ + private static LocalTime parseStartTime(String ct) throws ParseException { + String[] timePart = splitTiming(ct); + String startTime = timePart[0]; + String startTimeMinutes = startTime.split(":")[1]; + + if (!startTimeMinutes.equals("00") && !startTimeMinutes.equals("30")) { + throw new ParseException("Invalid start time"); + } + + return LocalTime.parse(startTime, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Parses the input class timing string to retrieve the End Time. + * + * @param ct ClassTiming string to be parsed. + * @return The end time of the input string. + * @throws ParseException if ct does not start at the hour or half hour mark. + */ + private static LocalTime parseEndTime(String ct) throws ParseException { + String[] timePart = splitTiming(ct); + String endTime = timePart[1]; + String endTimeMinutes = endTime.split(":")[1]; + + if (!endTimeMinutes.equals("00") && !endTimeMinutes.equals("30") && !endTime.equals("23:59")) { + throw new ParseException("Invalid end time"); + } + + return LocalTime.parse(endTime, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Gets the start time of the class timing. + * + * @return Start Time of ClassTiming. + */ + public LocalTime getStartTime() { + return this.startTime; + } + + /** + * Gets the end time of the class timing. + * + * @return End Time of ClassTiming. + */ + public LocalTime getEndTime() { + return this.endTime; + } + + /** + * Gets the day of the class timing. + * + * @return Day of ClassTiming. + */ + private String getDay() { + return this.day; + } + + /** + * Returns boolean true if other is on the same day as this, false otherwise. + * + * @param other ClassTiming to compare to this. + * @return Boolean on whether this and other is on the same day. + */ + public Boolean isSameDay(ClassTiming other) { + if (this.getDayToInt() == other.getDayToInt()) { + return true; + } else { + return false; + } + } + + /** + * Gets the timing string without the day of the ClassTiming. + * + * @return Timing of the ClassTiming without the Day. + */ + public String getClassTiming() { + return this.value.split(" ")[1]; + } + + /** + * Returns true if a given string is a valid class timing. + */ + public static boolean isValidClassTiming(String test) { + try { + if (test.matches(VALIDATION_REGEX)) { + LocalTime startTime = parseStartTime(test); + LocalTime endTime = parseEndTime(test); + return startTime.isBefore(endTime); + } else { + return false; + } + } catch (ParseException e) { + return false; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClassTiming // instanceof handles nulls + && value.equals(((ClassTiming) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(ClassTiming o) { + int thisDayInt = getDayToInt(); + + String otherDay = o.getDay(); + int otherDayInt = o.getDayToInt(); + LocalTime otherStartTime = o.getStartTime(); + + if (thisDayInt < otherDayInt) { + return -1; + } else if (otherDayInt < thisDayInt) { + return 1; + } else if (this.getStartTime().equals(otherStartTime)) { + return 0; + } else { + return otherStartTime.isAfter(this.getEndTime()) || this.getStartTime().isBefore(otherStartTime) ? -1 : 1; + } + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/Location.java b/src/main/java/seedu/times/model/tuitionclass/Location.java new file mode 100644 index 00000000000..771ec38f641 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/Location.java @@ -0,0 +1,58 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.model.util.Commons.MAX_CHAR_COUNT; + +/** + * Represents a {@code TuitionClass}'s class location in the Timestable. + * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)} + */ +public class Location { + + public static final String MESSAGE_CONSTRAINTS = "Class location should be between 1 and " + + MAX_CHAR_COUNT + " characters long!"; + + /* + * 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].{0,84}"; + + public final String value; + + /** + * Constructs an {@code Location}. + * + * @param location A valid class address. + */ + public Location(String location) { + requireNonNull(location); + checkArgument(isValidLocation(location), MESSAGE_CONSTRAINTS); + value = location; + } + + /** + * Returns true if a given string is a valid class location. + */ + public static boolean isValidLocation(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 Location // instanceof handles nulls + && value.equals(((Location) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/Rate.java b/src/main/java/seedu/times/model/tuitionclass/Rate.java new file mode 100644 index 00000000000..646a5c370b1 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/Rate.java @@ -0,0 +1,60 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.AppUtil.checkArgument; +import static seedu.times.model.util.Commons.MAX_CHAR_COUNT; + +/** + * Represents a {@code TuitionClass}'s rate in the Timestable. + */ +public class Rate { + + public static final String MESSAGE_CONSTRAINTS = + "Rate should satisfy the following constraints:\n" + + "1. Not exceed a million\n" + + "2. Not be negative\n" + + "3. Not be empty\n" + + "4. At most be up to 2 decimal places\n" + + "5. You also cannot enter a decimal point without following it up with at least one digit.\n"; + public static final String VALIDATION_REGEX = "\\d{1,6}(\\.\\d{1,2})?"; + + public final String value; + + /** + * Constructs a {@code Rate} + * + * @param rate A valid tuition per hour rate. + */ + public Rate(String rate) { + requireNonNull(rate); + checkArgument(isValidRate(rate), MESSAGE_CONSTRAINTS); + value = rate; + } + + /** + * Returns true if a given string is a valid tuition rate. + * + * @param test String representing rate to be tested. + * @return A boolean value. + */ + public static boolean isValidRate(String test) { + return test.matches(VALIDATION_REGEX) && test.length() < MAX_CHAR_COUNT; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Rate // instanceof handles nulls + && value.equals(((Rate) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/StudentNameList.java b/src/main/java/seedu/times/model/tuitionclass/StudentNameList.java new file mode 100644 index 00000000000..2bd507ea456 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/StudentNameList.java @@ -0,0 +1,182 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.times.model.person.Name; +import seedu.times.model.person.Person; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.exceptions.DuplicateStudentInClassException; + +/** + * Represents a unique list of names corresponding to the students + * that a {@code TuitionClass} has. + */ +public class StudentNameList implements Iterable { + + private final List internalList = new ArrayList<>(); + + /** + * Constructs an StudentNameList from an array of Strings. + * + * @param nameList The String array to convert into an StudentNameList. + */ + public StudentNameList(String[] nameList) { + for (String name : nameList) { + internalList.add(new Name(name)); + } + } + + /** + * Constructor that builds a StudentNameList from zero or more names. + * + * @param names Names to be stored in the StudentNameList. + */ + public StudentNameList(Name... names) { + for (Name name : names) { + this.add(name); + } + } + + /** + * Checks to see if the name in argument is already in the NameList. + * + * @param toCheck Name to check. + * @return boolean, true if name is already in list, false otherwise. + */ + public boolean contains(Name toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Removes name from the list. + * + * @param name to be removed. + */ + public void remove(Name name) { + internalList.remove(name); + } + + /** + * Removes all names provided from the list. + * @param names The list of names to be removed. + */ + public void removeAll(List names) { + internalList.removeAll(names); + } + + /** + * Adds a name to the list. + * + * @param toAdd name to be added. + * @throws DuplicateStudentInClassException if name is already in the list. + */ + public void add(Name toAdd) throws DuplicateStudentInClassException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentInClassException(toAdd); + } + internalList.add(toAdd); + } + + /** + * Adds all the names in the argument into the list. + * + * @param names to be added in StudentNameList form. + */ + public void addAll(StudentNameList names) { + for (Name name : names) { + if (!this.contains(name)) { + this.add(name); + } + } + } + + /** + * Adds all the names in argument into the list. + * + * @param names list of names to be added. + * @throws DuplicateStudentInClassException if there are duplicate names in list. + */ + public void addAll(List names) throws DuplicateStudentInClassException { + for (int i = 0; i < names.size(); i++) { + this.add(names.get(i)); + } + } + + public int size() { + return internalList.size(); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Sorts the internal Name list in the same order as the list of students given. + * + * @param listToSortBy The student list to sort the name list by. + */ + public void sortListByList(List listToSortBy) { + Collections.sort(internalList, + Comparator.comparing(name -> listToSortBy.stream().map(Person::getName) + .collect(Collectors.toList()).indexOf(name))); + } + + /** + * Gets the Name at the given index. + * + * @param zeroBasedIndex The index to retrieve the Name from. + * @return The Name at the given index. + */ + public Name get(int zeroBasedIndex) { + assert (zeroBasedIndex < internalList.size()); + return internalList.get(zeroBasedIndex); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof StudentNameList)) { + return false; + } + + return ((StudentNameList) o).internalList.equals(internalList); + } + + /** + * Replaces the old student name in the internalList with the new name. + * + * @param newName Name to replace the old name. + * @param oldName Name to be replaced by the new name. + */ + public void replaceStudentName(Name newName, Name oldName) { + int oldNameIndex = internalList.indexOf(oldName); + if (oldNameIndex != -1) { + internalList.set(oldNameIndex, newName); + } + } + + /** + * Utility method for testing + */ + public String[] getNames() { + int len = internalList.size(); + String[] res = new String[len]; + for (int i = 0; i < len; i++) { + res[i] = internalList.get(i).getFullName(); + } + return res; + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/TuitionClass.java b/src/main/java/seedu/times/model/tuitionclass/TuitionClass.java new file mode 100644 index 00000000000..1439f12ba79 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/TuitionClass.java @@ -0,0 +1,242 @@ +package seedu.times.model.tuitionclass; + +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalTime; +import java.util.Arrays; + +import seedu.times.model.person.Name; + +/** + * Represents a {@code TuitionClass} in the Timestable. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class TuitionClass { + + private final ClassTiming classTiming; + private final ClassName className; + private final Location location; + private final Rate rate; + + /** + * ArrayList of {@code Name} + * Rationale for choosing Name as identifier: + * If the {@code Student} objects are stored, any changes to a student would cause a cascade of updates in classes + * the student is in. + */ + private final StudentNameList studentNameList; + + /** + * Represents a tuition class for Students to join. A {@code TuitionClass} can have multiple {@code Student}s. + * A class is uniquely identified by its timing; a single timing can only have _one_ class. + *

+ * A {@code Student} can have multiple {@code TuitionClass}es as well. + * + * @param className The name of the class to be created. + * @param classTiming The timing of the class specified. This is the unique identifier (id) of the class. + * @param location The location of the class. + * @param rate How much it costs per hour to attend the class. + */ + public TuitionClass(ClassName className, ClassTiming classTiming, Location location, Rate rate, + StudentNameList studentNameList) { + requireAllNonNull(className, classTiming, location, rate); + this.className = className; + this.classTiming = classTiming; + this.location = location; + this.rate = rate; + this.studentNameList = studentNameList; + } + + /** + * Represents a tuition class for Students to join. A {@code TuitionClass} can have multiple {@code Student}s. + * A class is uniquely identified by its timing; a single timing can only have _one_ class. Without the + * StudentNameList. + *

+ * A {@code Student} can have multiple {@code TuitionClass}es as well. + * + * @param className The name of the class to be created. + * @param classTiming The timing of the class specified. This is the unique identifier (id) of the class. + * @param location The location of the class. + * @param rate How much it costs per hour to attend the class. + */ + public TuitionClass(ClassName className, ClassTiming classTiming, Location location, Rate rate) { + requireAllNonNull(className, classTiming, location, rate); + this.className = className; + this.classTiming = classTiming; + this.location = location; + this.rate = rate; + this.studentNameList = new StudentNameList(); + } + + public ClassName getClassName() { + return className; + } + + public String getClassNameString() { + return className.toString(); + } + + public ClassTiming getClassTiming() { + return classTiming; + } + + public LocalTime getStartTiming() { + return classTiming.getStartTime(); + } + + public LocalTime getEndTiming() { + return classTiming.getEndTime(); + } + + public Location getLocation() { + return location; + } + + public Rate getRate() { + return rate; + } + + /** + * Returns whether the tuition class contains the Student. + * + * @return whether the tuition class contains the Student or not. + */ + public boolean containsStudent(Name name) { + return studentNameList.contains(name); + } + + public StudentNameList getStudentList() { + return studentNameList; + } + + public void addStudent(Name name) { + studentNameList.add(name); + } + + public void addAllStudents(Name... names) { + studentNameList.addAll(Arrays.asList(names)); + } + + /** + * Removes student name from the name list. + * + * @param name to be removed. + * @return this after name has been removed. + */ + public TuitionClass removeStudent(Name name) { + if (studentNameList.contains(name)) { + studentNameList.remove(name); + } + return this; + } + + public void addStudentList(StudentNameList studentNameList) { + this.studentNameList.addAll(studentNameList); + } + + /** + * Checks if the TuitionClass is at this timing. + */ + public boolean isAtTiming(ClassTiming otherClassTiming) { + return classTiming.equals(otherClassTiming); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof TuitionClass)) { + return false; + } + + TuitionClass o = (TuitionClass) other; + return o.className.equals(getClassName()) + && o.classTiming.equals(getClassTiming()) + && o.rate.equals(getRate()) + && o.location.equals(getLocation()) + && o.studentNameList.equals(getStudentList()); + } + + /** + * Returns true if the class timing of the class to be checked overlaps with this class. + */ + public boolean isOverlapping(TuitionClass toCheck) { + if (this.equals(toCheck)) { + return true; + } else if (this.getClassTiming().isSameDay(toCheck.getClassTiming()) //on the same day + && ((toCheck.getStartTiming().compareTo(this.getEndTiming()) < 0 + && toCheck.getStartTiming().compareTo(this.getStartTiming()) >= 0) // && toCheck start time overlap + // with this + || (toCheck.getEndTiming().compareTo(this.getEndTiming()) <= 0 + && toCheck.getEndTiming().compareTo(this.getStartTiming()) > 0) //toCheck end time overlap with this + || (toCheck.getStartTiming().compareTo(this.getStartTiming()) <= 0 //toCheck starts earlier than this + && toCheck.getEndTiming().compareTo(this.getEndTiming()) >= 0))) { //toCheck ends later than this + return true; + } else { + return false; + } + } + + /** + * Checks if 2 TuitionClass are the same using ClassTiming. + * + * @param otherClass to be checked with this. + * @return boolean. + */ + public boolean isSameClass(TuitionClass otherClass) { + if (otherClass == this) { + return true; + } + + return otherClass != null + && otherClass.getClassTiming().equals(getClassTiming()); + } + + public boolean isAfter(LocalTime time) { + return this.classTiming.isAfter(time); + } + + public LocalTime getStartTime() { + return this.classTiming.getStartTime(); + } + + public LocalTime getEndTime() { + return this.classTiming.getEndTime(); + } + + public int getDayToInt() { + return this.classTiming.getDayToInt(); + } + + public void replaceStudentName(Name newName, Name oldName) { + studentNameList.replaceStudentName(newName, oldName); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + builder.append("Class Timing: ") + .append(getClassTiming()) + .append(" "); + + if (!getClassTiming().equals(getClassName())) { + builder.append(" Class Name: ") + .append(getClassName()) + .append(" "); + } + + builder.append("Location: ") + .append(getLocation()) + .append(" "); + + builder.append("Rate: ") + .append(getRate()) + .append(" "); + + return builder.toString(); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/UniqueClassList.java b/src/main/java/seedu/times/model/tuitionclass/UniqueClassList.java new file mode 100644 index 00000000000..318647fcc8f --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/UniqueClassList.java @@ -0,0 +1,176 @@ +package seedu.times.model.tuitionclass; + +import static java.util.Objects.requireNonNull; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.times.model.person.Name; +import seedu.times.model.tuitionclass.exceptions.DuplicateClassException; +import seedu.times.model.tuitionclass.exceptions.OverlappingClassException; +import seedu.times.model.tuitionclass.exceptions.TuitionClassNotFoundException; + +/** + * A list of classes that makes sure there is no overlap between class timings. + */ +public class UniqueClassList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Adds a class to the list. TuitionClass must not overlap in timing with existing classes. + * + * @param toAdd The tuition class to add. + */ + public void add(TuitionClass toAdd) throws OverlappingClassException { + requireNonNull(toAdd); + if (!isValidTiming(toAdd)) { + throw new OverlappingClassException(); + } + + internalList.add(toAdd); + } + + /** + * Deletes a TuitionClass from the list. + * + * @param toDelete TuitionClass to be deleted. + */ + public void delete(TuitionClass toDelete) { + requireNonNull(toDelete); + if (!internalList.remove(toDelete)) { + throw new TuitionClassNotFoundException(); + } + } + + /** + * Removes a name from all the classes in the list. + * + * @param name to be removed. + */ + public void removeStudent(Name name) { + for (int i = 0; i < internalList.size(); i++) { + TuitionClass tuitionClass = internalList.get(i); + StudentNameList nameList = tuitionClass.getStudentList(); + if (nameList.contains(name)) { + TuitionClass editedTuitionClass = tuitionClass.removeStudent(name); + this.setClass(tuitionClass, editedTuitionClass); + } + } + } + + public void setClasses(List classes) { + requireNonNull(classes); + //todo check that classes are unique + List editedClasses = new ArrayList<>(classes); + for (int i = 0; i < editedClasses.size(); i++) { + for (int j = i + 1; j < editedClasses.size(); j++) { + if (editedClasses.get(i).equals(editedClasses.get(j))) { + throw new DuplicateClassException(); + } + } + } + internalList.setAll(editedClasses); + //internalList.setAll(classes); + } + + /** + * Checks if the class timing is valid (no overlaps with current classes). + */ + public boolean isValidTiming(TuitionClass otherClass) { + requireNonNull(otherClass); + for (TuitionClass tuitionClass : internalList) { + if (tuitionClass.isOverlapping(otherClass)) { + return false; + } + } + return true; + } + + /** + * Checks if there is a class with the timing specified. + */ + public boolean classExistAt(ClassTiming timing) { + //TODO: probably need to modify cos need to consider overlap + return internalList.stream().anyMatch(c -> { + return c.getClassTiming().equals(timing); + }); + } + + /** + * Checks if there is an identical class in the internalMap. + */ + public boolean contains(TuitionClass tuitionClass) { + return internalList.stream().anyMatch(tuitionClass::isSameClass); + } + + /** + * Returns the class at the specified timing. + * Throws an error if the timing is invalid or no classes exist at that timing. + */ + public TuitionClass getClassAt(ClassTiming timing) { + requireNonNull(timing); + for (TuitionClass tuitionClass : internalList) { + if (tuitionClass.getClassTiming().equals(timing)) { + return tuitionClass; + } + } + //if no class at specified timing returns null + return null; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueClassList // instanceof handles nulls + && internalList.equals(((UniqueClassList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + public void setClass(TuitionClass target, TuitionClass editedClass) { + requireAllNonNull(target, editedClass); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TuitionClassNotFoundException(); + } + + if (!target.isSameClass(editedClass) && contains(editedClass)) { + throw new DuplicateClassException(); + } + + //check for overlapping ClassTiming. + for (TuitionClass tuitionClass : internalList) { + if (tuitionClass.isSameClass(target)) { + continue; + } else if (tuitionClass.isOverlapping(editedClass)) { + throw new OverlappingClassException(); + } + } + internalList.set(index, editedClass); + } + + //TODO: need a personsAreUnique method probably +} diff --git a/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateClassException.java b/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateClassException.java new file mode 100644 index 00000000000..4c7a03ee7d9 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateClassException.java @@ -0,0 +1,12 @@ +package seedu.times.model.tuitionclass.exceptions; + +/** + * Exception thrown when user attempts at adding to unique class list a tuition class that has same timing + * as an already existing tuition class + */ +public class DuplicateClassException extends RuntimeException { + public static final String DUPLICATE_CLASS_ERROR_MESSAGE = "Operation would result in classes with same timing!"; + public DuplicateClassException() { + super(DUPLICATE_CLASS_ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateStudentInClassException.java b/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateStudentInClassException.java new file mode 100644 index 00000000000..d21327c97c5 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/exceptions/DuplicateStudentInClassException.java @@ -0,0 +1,9 @@ +package seedu.times.model.tuitionclass.exceptions; + +import seedu.times.model.person.Name; + +public class DuplicateStudentInClassException extends RuntimeException { + public DuplicateStudentInClassException(Name duplicatedName) { + super(duplicatedName.toString()); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/exceptions/OverlappingClassException.java b/src/main/java/seedu/times/model/tuitionclass/exceptions/OverlappingClassException.java new file mode 100644 index 00000000000..09adbf60d2d --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/exceptions/OverlappingClassException.java @@ -0,0 +1,12 @@ +package seedu.times.model.tuitionclass.exceptions; + +/** + * Signals that the operation will result in a invalid class (Classes are considered invalid if they have overlapping + * timings with existing classes in the list). + */ +public class OverlappingClassException extends RuntimeException { + public static final String OVERLAP_ERROR_MESSAGE = "Operation would result in overlapping class timings"; + public OverlappingClassException() { + super(OVERLAP_ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/exceptions/TuitionClassNotFoundException.java b/src/main/java/seedu/times/model/tuitionclass/exceptions/TuitionClassNotFoundException.java new file mode 100644 index 00000000000..4f37ac7153b --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/exceptions/TuitionClassNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.times.model.tuitionclass.exceptions; + +/** + * Signals that the TuitionClass was not found. + */ +public class TuitionClassNotFoundException extends RuntimeException { + public TuitionClassNotFoundException() { + super("Tuition Class not found."); + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/predicates/ClassNameContainsKeywordsPredicate.java b/src/main/java/seedu/times/model/tuitionclass/predicates/ClassNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..de65243fc62 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/predicates/ClassNameContainsKeywordsPredicate.java @@ -0,0 +1,32 @@ +package seedu.times.model.tuitionclass.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Tests that a {@code TuitionClass}'s {@code ClassName} matches any of the keywords given. + */ +public class ClassNameContainsKeywordsPredicate implements Predicate { + + private final List keywords; + + public ClassNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(TuitionClass tuitionClass) { + String classNameLowerCase = tuitionClass.getClassName().className.toLowerCase(); + return keywords.stream() + .anyMatch(keyword -> classNameLowerCase.contains(keyword.toLowerCase())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClassNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ClassNameContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/times/model/tuitionclass/predicates/ClassTimingContainsKeywordsPredicate.java b/src/main/java/seedu/times/model/tuitionclass/predicates/ClassTimingContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e915453bc83 --- /dev/null +++ b/src/main/java/seedu/times/model/tuitionclass/predicates/ClassTimingContainsKeywordsPredicate.java @@ -0,0 +1,32 @@ +package seedu.times.model.tuitionclass.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.times.commons.util.StringUtil; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Tests that a {@code TuitionClass}'s {@code ClassTiming} matches any of the keywords given. + */ +public class ClassTimingContainsKeywordsPredicate implements Predicate { + + private final List keywords; + + public ClassTimingContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(TuitionClass tuitionClass) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsWordIgnoreCase(tuitionClass.getClassTiming().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClassTimingContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ClassTimingContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/times/model/util/Commons.java b/src/main/java/seedu/times/model/util/Commons.java new file mode 100644 index 00000000000..2f26fde736b --- /dev/null +++ b/src/main/java/seedu/times/model/util/Commons.java @@ -0,0 +1,8 @@ +package seedu.times.model.util; + +/** + * Contains shared Utils data + */ +public class Commons { + public static final int MAX_CHAR_COUNT = 85; +} diff --git a/src/main/java/seedu/times/model/util/SampleDataUtil.java b/src/main/java/seedu/times/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..2468ca2dd34 --- /dev/null +++ b/src/main/java/seedu/times/model/util/SampleDataUtil.java @@ -0,0 +1,197 @@ +package seedu.times.model.util; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.TimesTable; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Contains utility methods for populating {@code TimesTable} with sample data. + */ +public class SampleDataUtil { + private static Student[] getSamplePersons() { + return new Student[] { + new Student(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@gmail.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), + new Nok( + new Name("Elise Yeoh"), + new Phone("93292556"), + new Email("eliseyeoh@gmail.com"), + new Address("Blk 30 Geylang Street 29, #06-40") + ), + getTagSet("A Math", "Sec 4")), + new Student(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@gmail.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Nok( + new Name("Daniel Yu"), + new Phone("87338920"), + new Email("danielyu@gmail.com"), + new Address("Blk 28 Geylang Street 29, #08-30") + ), + getTagSet("C Math", "Sec 3")), + new Student(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@yahoo.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Nok( + new Name("Annabeth Parker"), + new Phone("94896758"), + new Email("annabethp@yahoo.com"), + new Address("Blk 6 Petir Road, #07-16") + ), + getTagSet("H2 Physics", "JC2")), + new Student(new Name("David Li"), new Phone("91031282"), new Email("lidavid@gmail.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new Nok( + new Name("Jacob Li"), + new Phone("84739056"), + new Email("jacobli@gmail.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43") + ), + getTagSet("A Math", "Sec 4")), + new Student(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@yahoo.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), + new Nok( + new Name("Hafez Ibrahim"), + new Phone("90308891"), + new Email("hafez@gmail.com"), + new Address("Blk 47 Tampines Street 20, #17-35") + ), + getTagSet("H2 Math", "JC1")), + new Student(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@gmail.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), + new Nok( + new Name("Bob Balakrishnan"), + new Phone("97776300"), + new Email("bobbyb@yahoo.com"), + new Address("Blk 45 Aljunied Street 85, #11-31") + ), + getTagSet("Chemistry", "Sec 3")), + new Student(new Name("Angelica Holcomb"), new Phone("85917748"), new Email("lorem@magnased.com"), + new Address("Ap #909-605 Ante St"), + new Nok( + new Name("Robert Holcomb"), + new Phone("85998446"), + new Email("robhol@yahoo.com"), + new Address("Ap #909-605 Ante St") + ), + getTagSet("Math", "Sec 2")), + new Student(new Name("Illana Page"), new Phone("86549382"), new Email("elit@dui.org"), + new Address("Ap #558-850 Amet, Rd"), + new Nok( + new Name("Annabeth Page"), + new Phone("97373300"), + new Email("annabp@yahoo.com"), + new Address("Ap #558-850 Amet, Rd") + ), + getTagSet("Physical Sciences", "Sec 1")), + new Student(new Name("Warren Campos"), new Phone("87718622"), new Email("magna@elit.ca"), + new Address("Ap #824-4482 Egestas Avenue"), + new Nok( + new Name("Boris Roth"), + new Phone("81308576"), + new Email("aliquet.vel.vulputate@phasellusdapibus.ca"), + new Address("987-5408 Dignissim Road") + ), + getTagSet("Chemistry", "Sec 1")) + }; + } + + private static TuitionClass[] getSampleClasses() { + return new TuitionClass[] { + new TuitionClass(new ClassName("Sec 4 A Math"), + new ClassTiming("MON 11:30-13:30"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("50"), + new StudentNameList(new Name("Alex Yeoh"), new Name("David Li"))), + new TuitionClass(new ClassName("Sec 3 C Math"), + new ClassTiming("MON 14:30-16:30"), + new Location("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Rate("60"), + new StudentNameList(new Name("Bernice Yu"))), + new TuitionClass(new ClassName("Sec 2 Physics"), + new ClassTiming("TUE 11:00-12:30"), + new Location("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Rate("40"), + new StudentNameList(new Name("Charlotte Oliveiro"))), + new TuitionClass(new ClassName("Sec 4 A Math 2"), + new ClassTiming("THU 11:30-13:30"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("50"), + new StudentNameList(new Name("Alex Yeoh"), new Name("David Li"))), + new TuitionClass(new ClassName("JC1 H2 Math"), + new ClassTiming("THU 16:30-18:30"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("50"), + new StudentNameList(new Name("Bernice Yu"))), + new TuitionClass(new ClassName("IB Year 5 HL Math"), + new ClassTiming("FRI 17:00-19:00"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("70"), + new StudentNameList(new Name("Warren Campos"))), + new TuitionClass(new ClassName("Sec 3 Chemistry"), + new ClassTiming("FRI 14:00-16:00"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("40"), + new StudentNameList(new Name("Angelica Holcomb"), new Name("David Li"))), + new TuitionClass(new ClassName("Sec 1 Math"), + new ClassTiming("SAT 11:30-13:00"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("40"), + new StudentNameList(new Name("Alex Yeoh"), new Name("Angelica Holcomb"))), + new TuitionClass(new ClassName("JC2 H2 Physics"), + new ClassTiming("SUN 11:30-13:30"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("60"), + new StudentNameList(new Name("Charlotte Oliveiro"), new Name("David Li"))), + new TuitionClass(new ClassName("JC2 H1 Math"), + new ClassTiming("SUN 14:30-16:30"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("50"), + new StudentNameList(new Name("Alex Yeoh"), new Name("David Li"))), + new TuitionClass(new ClassName("Sec 4 Physics"), + new ClassTiming("SAT 16:00-18:00"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("40"), + new StudentNameList(new Name("Alex Yeoh"), new Name("Irfan Ibrahim"))), + new TuitionClass(new ClassName("Sec 1 Physical Sciences"), + new ClassTiming("THU 09:30-11:00"), + new Location("Blk 30 Geylang Street 29, #06-40"), + new Rate("35"), + new StudentNameList(new Name("Illana Page"))) + }; + } + + public static ReadOnlyTimesTable getSampleTimesTable() { + TimesTable sampleAb = new TimesTable(); + for (Student sampleStudent : getSamplePersons()) { + sampleAb.addStudent(sampleStudent); + } + for (TuitionClass sampleClass : getSampleClasses()) { + sampleAb.addTuitionClass(sampleClass); + } + 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()); + } +} diff --git a/src/main/java/seedu/times/storage/JsonAdaptedNok.java b/src/main/java/seedu/times/storage/JsonAdaptedNok.java new file mode 100644 index 00000000000..17dc2d88568 --- /dev/null +++ b/src/main/java/seedu/times/storage/JsonAdaptedNok.java @@ -0,0 +1,88 @@ +package seedu.times.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; + +/** + * Jackson-friendly version of {@link Nok}. + */ +class JsonAdaptedNok { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Nok's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + + /** + * Constructs a {@code JsonAdaptedNok} with the given person details. + */ + @JsonCreator + public JsonAdaptedNok(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + } + + /** + * Converts a given {@code Nok} into this class for Jackson use. + */ + public JsonAdaptedNok(Nok source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Nok} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Nok toModelType() throws IllegalValueException { + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + return new Nok(modelName, modelPhone, modelEmail, modelAddress); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/times/storage/JsonAdaptedStudent.java similarity index 69% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/seedu/times/storage/JsonAdaptedStudent.java index a6321cec2ea..8e8a24c7d22 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/times/storage/JsonAdaptedStudent.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.times.storage; import java.util.ArrayList; import java.util.HashSet; @@ -9,18 +9,19 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; /** - * Jackson-friendly version of {@link Person}. + * Jackson-friendly version of {@link Student}. */ -class JsonAdaptedPerson { +class JsonAdaptedStudent { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; @@ -28,32 +29,37 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; + private final JsonAdaptedNok nok; private final List tagged = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + public JsonAdaptedStudent(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("nok") JsonAdaptedNok nok, + @JsonProperty("tagged") List tagged) { + this.name = name; this.phone = phone; this.email = email; this.address = address; + this.nok = nok; if (tagged != null) { this.tagged.addAll(tagged); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Student} into this class for Jackson use. */ - public JsonAdaptedPerson(Person source) { + public JsonAdaptedStudent(Student source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + nok = source.getNok() != null ? new JsonAdaptedNok(source.getNok()) : null; tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); @@ -64,7 +70,7 @@ public JsonAdaptedPerson(Person source) { * * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ - public Person toModelType() throws IllegalValueException { + public Student toModelType() throws IllegalValueException { final List personTags = new ArrayList<>(); for (JsonAdaptedTag tag : tagged) { personTags.add(tag.toModelType()); @@ -102,8 +108,11 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); + final Nok modelNok = nok == null ? null : nok.toModelType(); + final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + + return new Student(modelName, modelPhone, modelEmail, modelAddress, modelNok, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/times/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/seedu/times/storage/JsonAdaptedTag.java index 0df22bdb754..c01ec0ba670 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/times/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package seedu.times.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; +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.tag.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/seedu/times/storage/JsonAdaptedTuitionClass.java b/src/main/java/seedu/times/storage/JsonAdaptedTuitionClass.java new file mode 100644 index 00000000000..c106a9f6348 --- /dev/null +++ b/src/main/java/seedu/times/storage/JsonAdaptedTuitionClass.java @@ -0,0 +1,116 @@ +package seedu.times.storage; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.person.Name; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Jackson-friendly version of {@link TuitionClass}. + */ +public class JsonAdaptedTuitionClass { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Class's %s field is missing!"; + + private final String classTiming; + private final String className; + private final String location; + private final String rate; + private final List students = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedTuitionClass(@JsonProperty("classTiming") String classTiming, + @JsonProperty("className") String className, + @JsonProperty("rate") String rate, + @JsonProperty("location") String location, + @JsonProperty("students") List students) { + this.classTiming = classTiming; + this.className = className; + this.location = location; + this.rate = rate; + if (students != null) { + this.students.addAll(students); + } + } + + /** + * Converts a given {@code TuitionClass} into this class for Jackson use. + */ + public JsonAdaptedTuitionClass(TuitionClass source) { + classTiming = source.getClassTiming().value; + className = source.getClassName().className; + location = source.getLocation().value; + rate = source.getRate().value; + + Iterator iterator = source.getStudentList().iterator(); + while (iterator.hasNext()) { + students.add(iterator.next().toString()); + } + + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code TuitionClass} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted TuitionClass. + */ + public TuitionClass toModelType() throws IllegalValueException { + + if (classTiming == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ClassTiming.class.getSimpleName())); + } + if (!ClassTiming.isValidClassTiming(classTiming)) { + throw new IllegalValueException(ClassTiming.MESSAGE_CONSTRAINTS); + } + final ClassTiming modelTiming = new ClassTiming(classTiming); + + if (className == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ClassName.class.getSimpleName())); + } + if (!ClassName.isValidClassName(className)) { + throw new IllegalValueException(ClassName.MESSAGE_CONSTRAINTS); + } + final ClassName modelName = new ClassName(className); + + if (location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + if (!Location.isValidLocation(location)) { + throw new IllegalValueException(Location.MESSAGE_CONSTRAINTS); + } + final Location modelLocation = new Location(location); + + if (rate == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Rate.class.getSimpleName())); + } + if (!Rate.isValidRate(rate)) { + throw new IllegalValueException(Rate.MESSAGE_CONSTRAINTS); + } + final Rate modelRate = new Rate(rate); + + final StudentNameList tuitionClassNameList = new StudentNameList(); + for (String name : students) { + tuitionClassNameList.add(new Name(name)); + } + + return new TuitionClass(modelName, modelTiming, modelLocation, modelRate, tuitionClassNameList); + } +} diff --git a/src/main/java/seedu/times/storage/JsonSerializableTimesTable.java b/src/main/java/seedu/times/storage/JsonSerializableTimesTable.java new file mode 100644 index 00000000000..f6dcea219da --- /dev/null +++ b/src/main/java/seedu/times/storage/JsonSerializableTimesTable.java @@ -0,0 +1,77 @@ +package seedu.times.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.TimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * An Immutable TimesTable that is serializable to JSON format. + */ +@JsonRootName(value = "timestable") +class JsonSerializableTimesTable { + + public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_CLASS = "Class list contains duplicate class(es)."; + + private final List persons = new ArrayList<>(); + private final List classes = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableTimesTable} with the given persons. + */ + @JsonCreator + public JsonSerializableTimesTable(@JsonProperty("persons") List persons, + @JsonProperty("classes") List classes) { + this.persons.addAll(persons); + this.classes.addAll(classes); + } + + /** + * Converts a given {@code ReadOnlyTimesTable} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableTimesTable}. + */ + public JsonSerializableTimesTable(ReadOnlyTimesTable source) { + persons.addAll(source.getStudentList().stream().map(JsonAdaptedStudent::new).collect(Collectors.toList())); + classes.addAll(source.getTuitionClassList().stream().map(JsonAdaptedTuitionClass::new) + .collect(Collectors.toList())); + } + + /** + * Converts this address book into the model's {@code TimesTable} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public TimesTable toModelType() throws IllegalValueException { + TimesTable timesTable = new TimesTable(); + + for (JsonAdaptedStudent jsonAdaptedStudent : persons) { + Student student = jsonAdaptedStudent.toModelType(); + if (timesTable.hasPerson(student)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + timesTable.addStudent(student); + } + + for (JsonAdaptedTuitionClass jsonAdaptedTuitionClass : classes) { + TuitionClass tuitionClass = jsonAdaptedTuitionClass.toModelType(); + if (timesTable.hasTuitionClass(tuitionClass)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_CLASS); + } + timesTable.addTuitionClass(tuitionClass); + } + + return timesTable; + } + +} diff --git a/src/main/java/seedu/times/storage/JsonTimesTableStorage.java b/src/main/java/seedu/times/storage/JsonTimesTableStorage.java new file mode 100644 index 00000000000..851890b4a77 --- /dev/null +++ b/src/main/java/seedu/times/storage/JsonTimesTableStorage.java @@ -0,0 +1,80 @@ +package seedu.times.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.commons.util.FileUtil; +import seedu.times.commons.util.JsonUtil; +import seedu.times.model.ReadOnlyTimesTable; + +/** + * A class to access TimesTable data stored as a json file on the hard disk. + */ +public class JsonTimesTableStorage implements TimesTableStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonTimesTableStorage.class); + + private Path filePath; + + public JsonTimesTableStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getTimesTableFilePath() { + return filePath; + } + + @Override + public Optional readTimesTable() throws DataConversionException { + return readTimesTable(filePath); + } + + /** + * Similar to {@link #readTimesTable()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readTimesTable(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonTimesTable = JsonUtil.readJsonFile( + filePath, JsonSerializableTimesTable.class); + if (!jsonTimesTable.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonTimesTable.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveTimesTable(ReadOnlyTimesTable timesTable) throws IOException { + saveTimesTable(timesTable, filePath); + } + + /** + * Similar to {@link #saveTimesTable(ReadOnlyTimesTable)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveTimesTable(ReadOnlyTimesTable timesTable, Path filePath) throws IOException { + requireNonNull(timesTable); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableTimesTable(timesTable), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/times/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/times/storage/JsonUserPrefsStorage.java index bc2bbad84aa..cad9af77de6 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/times/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package seedu.times.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.commons.util.JsonUtil; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/times/storage/Storage.java b/src/main/java/seedu/times/storage/Storage.java new file mode 100644 index 00000000000..e5c33b7e2fc --- /dev/null +++ b/src/main/java/seedu/times/storage/Storage.java @@ -0,0 +1,32 @@ +package seedu.times.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends TimesTableStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getTimesTableFilePath(); + + @Override + Optional readTimesTable() throws DataConversionException, IOException; + + @Override + void saveTimesTable(ReadOnlyTimesTable timesTable) throws IOException; + +} diff --git a/src/main/java/seedu/times/storage/StorageManager.java b/src/main/java/seedu/times/storage/StorageManager.java new file mode 100644 index 00000000000..d24f24a0890 --- /dev/null +++ b/src/main/java/seedu/times/storage/StorageManager.java @@ -0,0 +1,79 @@ +package seedu.times.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.UserPrefs; + +/** + * Manages storage of TimesTable in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private TimesTableStorage timesTableStorage; + private UserPrefsStorage userPrefsStorage; + + /** + * Creates a {@code StorageManager} with the given {@code TimesTableStorage} and {@code UserPrefStorage}. + */ + public StorageManager(TimesTableStorage timesTableStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.timesTableStorage = timesTableStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public Path getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ TimesTable methods ============================== + + @Override + public Path getTimesTableFilePath() { + return timesTableStorage.getTimesTableFilePath(); + } + + @Override + public Optional readTimesTable() throws DataConversionException, IOException { + return readTimesTable(timesTableStorage.getTimesTableFilePath()); + } + + @Override + public Optional readTimesTable(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return timesTableStorage.readTimesTable(filePath); + } + + @Override + public void saveTimesTable(ReadOnlyTimesTable timesTable) throws IOException { + saveTimesTable(timesTable, timesTableStorage.getTimesTableFilePath()); + } + + @Override + public void saveTimesTable(ReadOnlyTimesTable timesTable, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + timesTableStorage.saveTimesTable(timesTable, filePath); + } + +} diff --git a/src/main/java/seedu/times/storage/TimesTableStorage.java b/src/main/java/seedu/times/storage/TimesTableStorage.java new file mode 100644 index 00000000000..528f6ceab4a --- /dev/null +++ b/src/main/java/seedu/times/storage/TimesTableStorage.java @@ -0,0 +1,46 @@ +package seedu.times.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.TimesTable; + +/** + * Represents a storage for {@link TimesTable}. + */ +public interface TimesTableStorage { + + /** + * Returns the file path of the data file. + */ + Path getTimesTableFilePath(); + + /** + * Returns TimeTable data as a {@link ReadOnlyTimesTable}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readTimesTable() throws DataConversionException, IOException; + + /** + * @see #getTimesTableFilePath() + */ + Optional readTimesTable(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTimesTable} to the storage. + * @param timesTable cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTimesTable(ReadOnlyTimesTable timesTable) throws IOException; + + /** + * @see #saveTimesTable(ReadOnlyTimesTable) + */ + void saveTimesTable(ReadOnlyTimesTable timesTable, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/times/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/times/storage/UserPrefsStorage.java index 29eef178dbc..7c9d7682854 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/times/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package seedu.times.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.times.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link seedu.times.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/times/ui/CommandBox.java similarity index 89% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/times/ui/CommandBox.java index 9e75478664b..2f955305cd5 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/times/ui/CommandBox.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package seedu.times.ui; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -77,7 +77,7 @@ public interface CommandExecutor { /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see seedu.times.logic.Logic#execute(String) */ CommandResult execute(String commandText) throws CommandException, ParseException; } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/times/ui/HelpWindow.java similarity index 92% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/times/ui/HelpWindow.java index 9a665915949..88621dc0756 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/times/ui/HelpWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import java.util.logging.Logger; @@ -8,14 +8,14 @@ import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import seedu.times.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2122s1-cs2103t-f11-1.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/times/ui/MainWindow.java similarity index 63% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/seedu/times/ui/MainWindow.java index 9106c3aa6e5..223353de1e2 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/times/ui/MainWindow.java @@ -1,27 +1,39 @@ -package seedu.address.ui; +package seedu.times.ui; +import static java.util.Objects.requireNonNull; + +import java.util.List; import java.util.logging.Logger; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.core.LogsCenter; +import seedu.times.logic.CommandObserver; +import seedu.times.logic.Logic; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.classtab.ClassPanel; +import seedu.times.ui.studenttab.StudentListPanel; +import seedu.times.ui.timetabletab.TimetablePanel; /** * The Main Window. Provides the basic application layout containing * a menu bar and space where other JavaFX elements can be placed. */ -public class MainWindow extends UiPart { +public class MainWindow extends UiPart implements CommandObserver { private static final String FXML = "MainWindow.fxml"; @@ -31,9 +43,11 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private StudentListPanel studentListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private ClassPanel classPanel; + private TimetablePanel timetablePanel; @FXML private StackPane commandBoxPlaceholder; @@ -42,7 +56,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane studentListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -50,6 +64,24 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane classListPanelPlaceholder; + + @FXML + private TabPane tabPane; + + @FXML + private Tab studentsTab; + + @FXML + private Tab tuitionClassTab; + + @FXML + private StackPane timetablePanelPlaceholder; + + @FXML + private Tab timetableTab; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -78,6 +110,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -110,17 +143,30 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); + + classPanel = new ClassPanel(logic.getFilteredStudentList(), logic.getFilteredTuitionClassList()); + classListPanelPlaceholder.getChildren().add(classPanel.getRoot()); + hideTuitionClassStudentList(); + + timetablePanel = new TimetablePanel(logic.getFilteredTuitionClassList()); + timetablePanelPlaceholder.getChildren().add(timetablePanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getTimesTableFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + studentsTab = new Tab("Students", studentListPanelPlaceholder); + timetableTab = new Tab("Timetable", timetablePanelPlaceholder); + tuitionClassTab = new Tab("Class", classListPanelPlaceholder); + + tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); } /** @@ -163,14 +209,39 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + @Override + public void updateClass(Integer indexOfClassToSelect) { + requireNonNull(indexOfClassToSelect); + List lastShownList = logic.getFilteredTuitionClassList(); + + ObservableList newStudentList; + + TuitionClass tuitionClass = lastShownList.get(indexOfClassToSelect); + newStudentList = logic.getFilteredStudentList().filtered(student -> + tuitionClass.containsStudent(student.getName())); + classPanel.setItems(newStudentList); } + @Override + public void hideTuitionClassStudentList() { + ObservableList newStudentList = logic.getFilteredStudentList().filtered(student -> false); + + classPanel.setItems(newStudentList); + } + + @Override + public void updateView(TabName tabToView) { + requireNonNull(tabToView); + tabPane.getSelectionModel().select(tabToView.getIndex()); + logger.info("Switched to " + tabToView + " because of updateView()"); + } + + + /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see seedu.times.logic.Logic#execute(String) */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/times/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/times/ui/ResultDisplay.java index 7d98e84eedf..e8e73dd3cab 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/times/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/times/ui/StatusBarFooter.java similarity index 96% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/times/ui/StatusBarFooter.java index b577f829423..a3b5707b06f 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/times/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/times/ui/StudentListViewCell.java b/src/main/java/seedu/times/ui/StudentListViewCell.java new file mode 100644 index 00000000000..f9b0a0bf67b --- /dev/null +++ b/src/main/java/seedu/times/ui/StudentListViewCell.java @@ -0,0 +1,22 @@ +package seedu.times.ui; + +import javafx.scene.control.ListCell; +import seedu.times.model.person.Student; +import seedu.times.ui.studenttab.StudentCard; + +/** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ +public class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentCard(student, getIndex() + 1).getRoot()); + } + } +} diff --git a/src/main/java/seedu/times/ui/TabName.java b/src/main/java/seedu/times/ui/TabName.java new file mode 100644 index 00000000000..8e26d990f3a --- /dev/null +++ b/src/main/java/seedu/times/ui/TabName.java @@ -0,0 +1,25 @@ +package seedu.times.ui; + +/** + * Represents the name of the tabs of Timestable. + */ +public enum TabName { + STUDENTS(0), + CLASSES(1), + TIMETABLE(2); + + private final int index; + + TabName(int index) { + this.index = index; + } + + /** + * Gets the index of the tab. + * + * @return Index of the tab in Timestable + */ + public int getIndex() { + return index; + } +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/times/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/times/ui/Ui.java index 17aa0b494fe..5bb84ce6294 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/times/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/times/ui/UiManager.java similarity index 87% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/times/ui/UiManager.java index 882027e4537..de9e3b616e3 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/times/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; +import seedu.times.MainApp; +import seedu.times.commons.core.LogsCenter; +import seedu.times.commons.util.StringUtil; +import seedu.times.logic.Logic; /** * The manager of the UI component. @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/TimestableLogo.png"; private Logic logic; private MainWindow mainWindow; @@ -42,6 +42,7 @@ public void start(Stage primaryStage) { try { mainWindow = new MainWindow(primaryStage, logic); + logic.setCommandObserver(this.mainWindow); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); @@ -66,7 +67,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.getDialogPane().getStylesheets().add("view/RedTheme.css"); alert.initOwner(owner); alert.setTitle(title); alert.setHeaderText(headerText); diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/times/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/times/ui/UiPart.java index fc820e01a9c..44e6a6ecbd4 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/times/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import seedu.times.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -84,5 +84,4 @@ private static URL getFxmlFileUrl(String fxmlFileName) { URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); return requireNonNull(fxmlFileUrl); } - } diff --git a/src/main/java/seedu/times/ui/classtab/ClassPanel.java b/src/main/java/seedu/times/ui/classtab/ClassPanel.java new file mode 100644 index 00000000000..dc2dd5a6afb --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/ClassPanel.java @@ -0,0 +1,65 @@ +package seedu.times.ui.classtab; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import seedu.times.commons.core.LogsCenter; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.UiPart; + +/** + * A UI for the Class Panel Tab. + */ +public class ClassPanel extends UiPart { + private static final String FXML = "classTab/ClassPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ClassPanel.class); + + @FXML + private StackPane tuitionClassListPlaceholder; + + @FXML + private StackPane studentListPlaceholder; + + // Labels + @FXML + private Label classLabel; + @FXML + private Label studentLabel; + + private final StudentClassPanel studentClassPanel; + + private final TuitionClassPanel tuitionClassPanel; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public ClassPanel(ObservableList studentList, ObservableList tuitionClassList) { + super(FXML); + //right side + this.studentClassPanel = new StudentClassPanel(studentList); + + //left side + this.tuitionClassPanel = new TuitionClassPanel(studentList, tuitionClassList); + + tuitionClassPanel.setStudentClassList(studentClassPanel.getStudentListView()); + + studentListPlaceholder.getChildren().add(studentClassPanel.getRoot()); + tuitionClassListPlaceholder.getChildren().add(tuitionClassPanel.getRoot()); + + // Set UI stuff + classLabel.setText("Your Classes"); + studentLabel.setText("Students"); + + logger.info("ClassPanel tab opened"); + } + + public void setItems(ObservableList studentObservableList) { + studentClassPanel.setItems(studentObservableList); + } + +} diff --git a/src/main/java/seedu/times/ui/classtab/StudentClassPanel.java b/src/main/java/seedu/times/ui/classtab/StudentClassPanel.java new file mode 100644 index 00000000000..59cbea80a19 --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/StudentClassPanel.java @@ -0,0 +1,38 @@ +package seedu.times.ui.classtab; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.times.model.person.Student; +import seedu.times.ui.UiPart; + +/** + * Represents a Panel for the Students in a Tuition Class. + */ +public class StudentClassPanel extends UiPart { + private static final String FXML = "classTab/StudentClassListPanel.fxml"; + + @FXML + private ListView studentListView; + + /** + * Represents a Panel for the Students in a Tuition Class. + * + * @param studentList The studentList that it is supposed to display. + */ + public StudentClassPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListClassTabViewCell()); + } + + + public void setItems(ObservableList studentObservableList) { + studentListView.setItems(studentObservableList); + } + + public ListView getStudentListView() { + return studentListView; + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/times/ui/classtab/StudentClassTabCard.java similarity index 56% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/times/ui/classtab/StudentClassTabCard.java index 7fc927bc5d9..afdd4761f1b 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/times/ui/classtab/StudentClassTabCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui.classtab; import java.util.Comparator; @@ -7,14 +7,18 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.times.model.person.Student; +import seedu.times.ui.UiPart; /** * An UI component that displays information of a {@code Person}. */ -public class PersonCard extends UiPart { +public class StudentClassTabCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "classTab/StudentListCard.fxml"; + private static final String ADDRESS_FIELD = "Address: "; + private static final String MOBILE_FIELD = "Mobile: "; + private static final String EMAIL_FIELD = "Email: "; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,8 +28,9 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Student student; + // Student @FXML private HBox cardPane; @FXML @@ -42,17 +47,20 @@ public class PersonCard extends UiPart { private FlowPane tags; /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code Student} with the given {@code Student} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public StudentClassTabCard(Student student, int displayedIndex) { super(FXML); - this.person = person; + this.student = student; + + // Student id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() + name.setText(student.getName().fullName); + phone.setText(MOBILE_FIELD + student.getPhone().value); + address.setText(ADDRESS_FIELD + student.getAddress().value); + email.setText(EMAIL_FIELD + student.getEmail().value); + + student.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @@ -65,13 +73,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof StudentClassTabCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + StudentClassTabCard card = (StudentClassTabCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && student.equals(card.student); } } diff --git a/src/main/java/seedu/times/ui/classtab/StudentListClassTabViewCell.java b/src/main/java/seedu/times/ui/classtab/StudentListClassTabViewCell.java new file mode 100644 index 00000000000..a44a197bac8 --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/StudentListClassTabViewCell.java @@ -0,0 +1,21 @@ +package seedu.times.ui.classtab; + +import javafx.scene.control.ListCell; +import seedu.times.model.person.Student; + +/** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ +public class StudentListClassTabViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentClassTabCard(student, getIndex() + 1).getRoot()); + } + } +} diff --git a/src/main/java/seedu/times/ui/classtab/TuitionClassCard.java b/src/main/java/seedu/times/ui/classtab/TuitionClassCard.java new file mode 100644 index 00000000000..222c1d12259 --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/TuitionClassCard.java @@ -0,0 +1,106 @@ +package seedu.times.ui.classtab; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.UiPart; + +/** + * Represents a Tuition Card to be shown in the GUI. + */ +public class TuitionClassCard extends UiPart { + + private static final String FXML = "classTab/TuitionClassListCard.fxml"; + private static final Character DOLLAR_SIGN = '$'; + private static final String PER_HOUR = "/hr"; + private static final String CLASS_TIMING_FIELD = "Timing: "; + private static final String LOCATION_FIELD = "Location: "; + private static final String RATE_FIELD = "Rate: "; + private static final String CLASS_SIZE_FIELD = "Size: "; + + public final TuitionClass tuitionClass; + + // TuitionClass + @FXML + private HBox cardPane; + @FXML + private Label className; + @FXML + private Label id; + @FXML + private Label classTiming; + @FXML + private Label classLocation; + @FXML + private Label rate; + @FXML + private Label classSize; + + private final ListView tuitionClassListView; + + private final ObservableList studentList; + + /** + * Represents a Tuition Card to be shown in the GUI. + * + * @param tuitionClass The class to display. + * @param displayedIndex The index to display. + */ + public TuitionClassCard(TuitionClass tuitionClass, int displayedIndex, + ObservableList studentList, ListView tuitionClassListView) { + super(FXML); + this.tuitionClass = tuitionClass; + this.studentList = studentList; + this.tuitionClassListView = tuitionClassListView; + + int index = tuitionClassListView.getSelectionModel().getSelectedIndex(); + tuitionClassListView.getSelectionModel().clearSelection(index); + + // TuitionClass + id.setText(displayedIndex + ". "); + className.setText(tuitionClass.getClassName().className); + classTiming.setText(CLASS_TIMING_FIELD + tuitionClass.getClassTiming().value); + classLocation.setText(LOCATION_FIELD + tuitionClass.getLocation().value); + rate.setText(RATE_FIELD + DOLLAR_SIGN + tuitionClass.getRate().value + PER_HOUR); + classSize.setText(CLASS_SIZE_FIELD + String.valueOf(tuitionClass.getStudentList().size())); + } + + @FXML + private void onMouseClick() { + selectTuitionClass(); + } + + /** + * Visually selects the tuition class and shows the Student List associated to it. + */ + public void selectTuitionClass() { + ObservableList newStudentList = + studentList.filtered(student -> tuitionClass.containsStudent(student.getName())); + + tuitionClassListView.setItems(newStudentList); + tuitionClassListView.setCellFactory(listView -> new StudentListClassTabViewCell()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TuitionClassCard)) { + return false; + } + + // state check + TuitionClassCard card = (TuitionClassCard) other; + return id.getText().equals(card.id.getText()) + && tuitionClass.equals(card.tuitionClass); + } +} diff --git a/src/main/java/seedu/times/ui/classtab/TuitionClassListViewCell.java b/src/main/java/seedu/times/ui/classtab/TuitionClassListViewCell.java new file mode 100644 index 00000000000..c44587f81d0 --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/TuitionClassListViewCell.java @@ -0,0 +1,41 @@ +package seedu.times.ui.classtab; + +import javafx.collections.ObservableList; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * Custom {@code ListCell} that displays the graphics of a {@code TuitionClass} using a {@code TuitionClassCard}. + */ +public class TuitionClassListViewCell extends ListCell { + + private final ObservableList studentList; + private final ListView studentClassListView; + + + /** + * Represents the cell displaying the tuition class. + * + * @param studentList The student list associated with the tuition class. + * @param studentClassListView The UI associated with the tuition class. + */ + public TuitionClassListViewCell(ObservableList studentList, ListView studentClassListView) { + this.studentList = studentList; + this.studentClassListView = studentClassListView; + } + + @Override + protected void updateItem(TuitionClass tuitionClass, boolean empty) { + super.updateItem(tuitionClass, empty); + + if (empty || tuitionClass == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TuitionClassCard(tuitionClass, getIndex() + 1, studentList, + studentClassListView).getRoot()); + } + } +} diff --git a/src/main/java/seedu/times/ui/classtab/TuitionClassPanel.java b/src/main/java/seedu/times/ui/classtab/TuitionClassPanel.java new file mode 100644 index 00000000000..9c0f0322691 --- /dev/null +++ b/src/main/java/seedu/times/ui/classtab/TuitionClassPanel.java @@ -0,0 +1,43 @@ +package seedu.times.ui.classtab; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.UiPart; + +/** + * Represents a Panel for the Tuition Class. + */ +public class TuitionClassPanel extends UiPart { + private static final String FXML = "classTab/TuitionClassListPanel.fxml"; + + private final ObservableList studentList; + + @FXML + private ListView tuitionClassListView; + + /** + * Represents a Panel for the Tuition Class. + * + * @param studentList The studentList is required for the Panel to filter out the students in the class. + * @param tuitionClassList The tuitionClassList is required to show the classes in the panel. + */ + public TuitionClassPanel(ObservableList studentList, ObservableList tuitionClassList) { + super(FXML); + this.studentList = studentList; + tuitionClassListView.setItems(tuitionClassList); + } + + /** + * Creates the students given the studentListView. + * + * @param studentListView The given studentListView to create the students with. + */ + public void setStudentClassList(ListView studentListView) { + tuitionClassListView.setCellFactory(listView -> + new TuitionClassListViewCell(studentList, studentListView)); + } +} diff --git a/src/main/java/seedu/times/ui/studenttab/StudentCard.java b/src/main/java/seedu/times/ui/studenttab/StudentCard.java new file mode 100644 index 00000000000..9df2c23ebd2 --- /dev/null +++ b/src/main/java/seedu/times/ui/studenttab/StudentCard.java @@ -0,0 +1,109 @@ +package seedu.times.ui.studenttab; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.times.model.person.Student; +import seedu.times.ui.UiPart; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class StudentCard extends UiPart { + + private static final String FXML = "StudentListCard.fxml"; + private static final String ADDRESS_FIELD = "Address: "; + private static final String MOBILE_FIELD = "Mobile: "; + private static final String EMAIL_FIELD = "Email: "; + + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Student student; + + // Student + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private FlowPane tags; + + // Nok + @FXML + private Label nokName; + @FXML + private Label nokPhone; + @FXML + private Label nokAddress; + @FXML + private Label nokEmail; + + /** + * Creates a {@code Student} with the given {@code Student} and index to display. + */ + public StudentCard(Student student, int displayedIndex) { + super(FXML); + this.student = student; + + // Student + id.setText(displayedIndex + ". "); + name.setText(student.getName().fullName); + phone.setText(MOBILE_FIELD + student.getPhone().value); + address.setText(ADDRESS_FIELD + student.getAddress().value); + email.setText(EMAIL_FIELD + student.getEmail().value); + + // Nok + if (student.getNok() != null) { + nokName.setText(student.getNok().getName().fullName); + nokPhone.setText(student.getNok().getPhone().value); + nokAddress.setText(student.getNok().getAddress().value); + nokEmail.setText(student.getNok().getEmail().value); + } else { + nokName.setText(""); + nokPhone.setText(""); + nokAddress.setText(""); + nokEmail.setText(""); + } + + student.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentCard)) { + return false; + } + + // state check + StudentCard card = (StudentCard) other; + return id.getText().equals(card.id.getText()) + && student.equals(card.student); + } +} diff --git a/src/main/java/seedu/times/ui/studenttab/StudentListPanel.java b/src/main/java/seedu/times/ui/studenttab/StudentListPanel.java new file mode 100644 index 00000000000..82dc4147e1b --- /dev/null +++ b/src/main/java/seedu/times/ui/studenttab/StudentListPanel.java @@ -0,0 +1,32 @@ +package seedu.times.ui.studenttab; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.times.commons.core.LogsCenter; +import seedu.times.model.person.Student; +import seedu.times.ui.StudentListViewCell; +import seedu.times.ui.UiPart; + +/** + * Panel containing the list of persons. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "classTab/StudentClassListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public StudentListPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + } +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableDay.java b/src/main/java/seedu/times/ui/timetabletab/TimetableDay.java new file mode 100644 index 00000000000..0866b01008d --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableDay.java @@ -0,0 +1,38 @@ +package seedu.times.ui.timetabletab; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; + +// Solution below adapted from +// https://github.com/AY1920S2-CS2103-W15-4/main/blob/master/src/main/java/clzzz/helper/ui/calendar/CalendarDate.java +/** + * A Ui to display the Days at the left side of the timetable. + */ +public class TimetableDay extends TimetableRegion { + + private static final int WIDTH = 50; + + private static final String FXML = "timetableTab/TimetableDay.fxml"; + + @FXML + private Label day; + + /** + * Creates a new Timetable Day. + * + * @param dayStr Day to be created. + */ + public TimetableDay(String dayStr) { + super(FXML, WIDTH); + day.setText(dayStr); + } + + /** + * Gets the width of the TimetableDay UI. + * + * @return Width of the Timetable Day UI. + */ + public static int getWidth() { + return WIDTH; + } +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableEmptySlot.java b/src/main/java/seedu/times/ui/timetabletab/TimetableEmptySlot.java new file mode 100644 index 00000000000..ca0c552b519 --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableEmptySlot.java @@ -0,0 +1,24 @@ +package seedu.times.ui.timetabletab; + +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; + +// Solution below adapted from +// https://github.com/AY1920S2-CS2103-W15-4/main/blob/master/src/main/java/clzzz/helper/ui/calendar/CalendarBuffer.java +/** + * A UI for the empty slot with no tuition class in the timetable. + */ +public class TimetableEmptySlot extends TimetableRegion { + + private static final String FXML = "timetableTab/TimetableEmptySlot.fxml"; + + /** + * Builds the empty slot with no tuition class. + * + * @param startTime Start Timing of empty slot. + * @param endTime End Timing of empty slot. + */ + public TimetableEmptySlot(LocalTime startTime, LocalTime endTime) { + super(FXML, startTime.until(endTime, ChronoUnit.MINUTES)); + } +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableHeader.java b/src/main/java/seedu/times/ui/timetabletab/TimetableHeader.java new file mode 100644 index 00000000000..12dc429e20e --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableHeader.java @@ -0,0 +1,26 @@ +package seedu.times.ui.timetabletab; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; + +/** + * A UI for the header of the timetable. + */ +public class TimetableHeader extends TimetableRegion { + private static final String FXML = "timetableTab/TimetableHeader.fxml"; + + @FXML + private Label headerLabel; + + /** + * Creates a new Timetable header with a string as its input and its width. + * + * @param headerStr String to be displayed on the header. + * @param width Width of the header. + */ + public TimetableHeader(String headerStr, int width) { + super(FXML, width); + this.headerLabel.setText(headerStr); + } + +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderLabel.java b/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderLabel.java new file mode 100644 index 00000000000..885c1e93b8c --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderLabel.java @@ -0,0 +1,18 @@ +package seedu.times.ui.timetabletab; + +/** + * A UI for the header label for the timetable. + */ +public class TimetableHeaderLabel extends TimetableHeader { + + /** + * Creates a new TimetableHeaderLabel with a string as its input and its width. + * + * @param headerStr String to be displayed on the header. + * @param width Width of the header. + */ + public TimetableHeaderLabel(String headerStr, int width) { + super(headerStr, width); + } + +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderTiming.java b/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderTiming.java new file mode 100644 index 00000000000..1b8f093fd27 --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableHeaderTiming.java @@ -0,0 +1,22 @@ +package seedu.times.ui.timetabletab; + +import java.time.LocalTime; + +/** + * A UI for the header timing of the timetable. + */ +public class TimetableHeaderTiming extends TimetableHeader { + + private static final int TIMETABLE_HEADER_SLOT_LENGTH = 60; + + /** + * Creates a new TimetableHeaderTiming with a start time and end time. + * + * @param headerStartTime Start time of the header. + * @param headerEndTime End time of the header. + */ + public TimetableHeaderTiming(LocalTime headerStartTime, LocalTime headerEndTime) { + super(headerStartTime.toString() + "-" + headerEndTime.toString(), TIMETABLE_HEADER_SLOT_LENGTH); + } + +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetablePanel.java b/src/main/java/seedu/times/ui/timetabletab/TimetablePanel.java new file mode 100644 index 00000000000..0ba9ee38211 --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetablePanel.java @@ -0,0 +1,313 @@ +package seedu.times.ui.timetabletab; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import seedu.times.commons.core.LogsCenter; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.ui.UiPart; + + +// Solution below adapted from +// https://github.com/AY1920S2-CS2103-W15-4/main/blob/master/src/main/java/clzzz/helper/ui/calendar/CalendarPanel.java +/** + * A UI for the Timetable Panel Tab. + */ +public class TimetablePanel extends UiPart { + private static final String FXML = "timetableTab/TimetablePanel.fxml"; + + private static final LocalTime DEFAULT_LATEST_HOUR = LocalTime.parse("18:00", + DateTimeFormatter.ofPattern("HH:mm")); + private static final LocalTime DEFAULT_EARLIEST_HOUR = LocalTime.parse("09:00", + DateTimeFormatter.ofPattern("HH:mm")); + + private final Logger logger = LogsCenter.getLogger(TimetablePanel.class); + + @FXML + private ScrollPane scrollPane; + + @FXML + private GridPane timetable; + + /** + * Creates a {@code TimetablePanel} with the given {@code ObservableList}. + */ + public TimetablePanel(ObservableList tuitionClasses) { + super(FXML); + build(tuitionClasses); + timetable.maxWidth(1539); + if (tuitionClasses != null) { + tuitionClasses.addListener(new ListChangeListener() { + @Override + public void onChanged(Change change) { + while (change.next()) { + build(tuitionClasses); + logger.info("Changes made to uniqueClassList, rebuilding timetable."); + } + } + }); + } + } + + /** + * Builds the UI of the timetable panel. + * + * @param tuitionClasses List of tuitionClass to retrieve the tuition class timings from for the timetable. + */ + public void build(ObservableList tuitionClasses) { + requireNonNull(tuitionClasses); + + clearTimetable(); + buildHeader(tuitionClasses); + buildDays(); + if (tuitionClasses.isEmpty()) { + logger.info("No class in uniqueClassList."); + } else { + logger.info("Building timetable from uniqueClassList."); + buildClasses(tuitionClasses); + } + } + + /** + * Builds the header panel for the timetable panel ui. + * + * @param tuitionClasses List of tuitionClasses to retrieve the earliest tuition class start timing and + * latest end timing. + */ + public void buildHeader(ObservableList tuitionClasses) { + requireNonNull(tuitionClasses); + + LocalTime earliestHour = tuitionClasses.size() == 0 ? DEFAULT_EARLIEST_HOUR : getEarliestHour(tuitionClasses); + LocalTime latestHour = tuitionClasses.size() == 0 ? DEFAULT_LATEST_HOUR : getLatestHour(tuitionClasses); + + timetable.add(new TimetableHeaderLabel("Time Slots", TimetableDay.getWidth()).getRoot(), + 0, 0, 50, 1); + + int columnIndex = TimetableDay.getWidth(); + + while (earliestHour.isBefore(latestHour) || earliestHour.isBefore(DEFAULT_LATEST_HOUR)) { + if (earliestHour.equals(LocalTime.parse("23:30", DateTimeFormatter.ofPattern("HH:mm")))) { + timetable.add(new TimetableHeaderTiming(earliestHour, earliestHour.plusMinutes(29)).getRoot(), + columnIndex, 0, 15, 1); + break; + } else { + timetable.add(new TimetableHeaderTiming(earliestHour, earliestHour.plusMinutes(30)).getRoot(), + columnIndex, 0, 15, 1); + } + columnIndex += 30; + + earliestHour = earliestHour.plusMinutes(30); + } + + } + + /** + * Builds the tuition class slot UI for the timetable. + * + * @param tuitionClasses List of tuition classes to retrieve the tuition class timings. + */ + public void buildClasses(ObservableList tuitionClasses) { + requireNonNull(tuitionClasses); + + LocalTime earliestHour = getEarliestHour(tuitionClasses); + + //earliest time is after the date + ArrayList sortedList = new ArrayList<>(tuitionClasses); + sortedList.sort(Comparator.comparing(TuitionClass::getClassTiming)); + + // build the timetable + LocalTime previousTime = earliestHour; + for (int i = 0; i < sortedList.size(); i++) { + TuitionClass currentTuitionClass = sortedList.get(i); + if (currentTuitionClass.isAfter(previousTime)) { + addEmptySlot(previousTime, currentTuitionClass, getColumnIndex(earliestHour, previousTime)); + } + + addTuitionClassSlot(currentTuitionClass, earliestHour); + + if (i != sortedList.size() - 1 + && currentTuitionClass.getDayToInt() < sortedList.get(i + 1).getDayToInt()) { + previousTime = earliestHour; + } else { + previousTime = currentTuitionClass.getEndTime(); + } + } + } + + /** + * Creates a tuition class slot to be displayed on the timetable. + * + * @param tuitionClass TuitionClass to add + * @param earliestTime Earliest class start time of the timetable. + */ + private void addTuitionClassSlot(TuitionClass tuitionClass, LocalTime earliestTime) { + assert !tuitionClass.getStartTime().isBefore(earliestTime); + + TimetableTuitionClassSlot timetableTuitionClassSlot = + new TimetableTuitionClassSlot(tuitionClass); + + int duration = getTimeDifference(tuitionClass.getStartTime(), tuitionClass.getEndTime()); + int columnIndex = getColumnIndex(earliestTime, tuitionClass.getStartTime()); + + timetable.add(timetableTuitionClassSlot.getRoot(), columnIndex, tuitionClass.getDayToInt(), + duration, 1); + } + + /** + * Gets the column index which the tuition class timing starts from on the timetable. + * + * @param earliestTime Earliest class start time of the timetable. + * @param timeToIndex Time which we want to find the column index of. + * @return Column index of the timeToIndex. + */ + private int getColumnIndex(LocalTime earliestTime, LocalTime timeToIndex) { + return TimetableDay.getWidth() + getTimeDifference(earliestTime, timeToIndex); + } + + /** + * Creates an empty slot to be displayed on the timetable. + * + * @param startTime Start Time of the empty slot. + * @param tuitionClass TuitionClass after the empty slot. + * @param column Column to add the empty slot to. + */ + private void addEmptySlot(LocalTime startTime, TuitionClass tuitionClass, int column) { + LocalTime endTime = tuitionClass.getStartTime(); + assert startTime.isBefore(endTime); + + TimetableEmptySlot emptySlot = new TimetableEmptySlot(startTime, endTime); + int duration = getTimeDifference(startTime, endTime); + + timetable.add(emptySlot.getRoot(), column, tuitionClass.getDayToInt(), duration, 1); + } + + /** + * Gets the time difference from two timings. + * + * @param startTime Start time of the two timings. + * @param endTime End time of the two timings. + * @return Time difference between the start time and the end time. + */ + public int getTimeDifference(LocalTime startTime, LocalTime endTime) { + assert startTime.isBefore(endTime) || startTime.equals(endTime); + + return (int) startTime.until(endTime, ChronoUnit.MINUTES); + } + + /** + * Gets the earliest hour of the earliest class timing from the list of students. + * + * @param tuitionClasses List of tuition classes to get the earliest hour from. + * @return Earliest hour of the earliest class timing. + */ + public LocalTime getEarliestHour(ObservableList tuitionClasses) { + assert tuitionClasses != null && tuitionClasses.size() > 0; + + LocalTime earliestTime = getEarliestTime(tuitionClasses); + + if (earliestTime.isAfter(DEFAULT_EARLIEST_HOUR)) { + return DEFAULT_EARLIEST_HOUR; + } + + String earliestTimeStr = earliestTime.toString(); + String earliestHourWithoutMinutes = earliestTimeStr.split(":")[0]; + String earliestHourStr = earliestHourWithoutMinutes + ":00"; + + return LocalTime.parse(earliestHourStr, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Gets the earliest class timing of the list of students. + * + * @param tuitionClasses List of students to get the earliest class timing from. + * @return Earliest class timing from StudentNameList. + */ + public LocalTime getEarliestTime(ObservableList tuitionClasses) { + assert tuitionClasses != null && tuitionClasses.size() > 0; + + return tuitionClasses.stream() + .map(TuitionClass::getStartTiming) + .reduce((classTime1, classTime2) -> classTime1.isBefore(classTime2) ? classTime1 : classTime2) + .get(); + } + + /** + * Gets the latest hour which all classes has ended. + * + * @param tuitionClasses List of students to get the latest hour from. + * @return Latest hour which all the classes has ended. + */ + public LocalTime getLatestHour(ObservableList tuitionClasses) { + assert tuitionClasses != null && tuitionClasses.size() > 0; + + LocalTime latestTime = getLatestTime(tuitionClasses); + + if (latestTime.isBefore(DEFAULT_LATEST_HOUR)) { + return DEFAULT_LATEST_HOUR; + } + + String latestTimeStr = latestTime.toString(); + String latestHourMinutes = latestTimeStr.split(":")[1]; + + if (latestHourMinutes.equals("00")) { + return latestTime; + } else { + String latestHourWithoutMinutes = latestTimeStr.split(":")[0]; + String latestHourStr = latestHourWithoutMinutes + ":00"; + LocalTime latestHour = LocalTime.parse(latestHourStr, DateTimeFormatter.ofPattern("HH:mm")); + + return latestHour.equals(LocalTime.parse("23:00", DateTimeFormatter.ofPattern("HH:mm"))) + ? latestHour.plusMinutes(59) + : latestHour.plusHours(1); + } + } + + /** + * Gets the latest class end time from the list of students. + * + * @param tuitionClasses List of students to get the latest class end time from. + * @return Latest class end time. + */ + public LocalTime getLatestTime(ObservableList tuitionClasses) { + assert tuitionClasses != null && tuitionClasses.size() > 0; + + return tuitionClasses.stream() + .map(TuitionClass::getEndTiming) + .reduce((classTime1, classTime2) -> classTime1.isAfter(classTime2) ? classTime1 : classTime2) + .get(); + } + + /** + * Builds the days side panel of the timetable panel ui. + */ + public void buildDays() { + timetable.add(new TimetableDay("Mon").getRoot(), 0, 1, 50, 1); + timetable.add(new TimetableDay("Tue").getRoot(), 0, 2, 50, 1); + timetable.add(new TimetableDay("Wed").getRoot(), 0, 3, 50, 1); + timetable.add(new TimetableDay("Thu").getRoot(), 0, 4, 50, 1); + timetable.add(new TimetableDay("Fri").getRoot(), 0, 5, 50, 1); + timetable.add(new TimetableDay("Sat").getRoot(), 0, 6, 50, 1); + timetable.add(new TimetableDay("Sun").getRoot(), 0, 7, 50, 1); + } + + /** + * Clears the previously constructed timetable. + */ + private void clearTimetable() { + timetable.getChildren().clear(); + timetable.getRowConstraints().clear(); + timetable.getColumnConstraints().clear(); + } +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableRegion.java b/src/main/java/seedu/times/ui/timetabletab/TimetableRegion.java new file mode 100644 index 00000000000..df8f18a1df5 --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableRegion.java @@ -0,0 +1,30 @@ +package seedu.times.ui.timetabletab; + +import javafx.fxml.FXML; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.times.ui.UiPart; + +// Solution below adapted from +// https://github.com/AY1920S2-CS2103-W15-4/main/blob/master/src/main/java/clzzz/helper/ui/calendar/CalendarRegion.java +/** + * A UI for the timetable region. + */ +public class TimetableRegion extends UiPart { + + @FXML + private HBox slot; + + /** + * Creates the timetable region UI. + * + * @param fxml Specified file path of the fxml to construct the timetable region. + * @param scale Amount to scale the Ui by. + */ + public TimetableRegion(String fxml, long scale) { + super(fxml); + slot.setPrefWidth(scale * 2); + slot.setMinWidth(scale * 2); + } + +} diff --git a/src/main/java/seedu/times/ui/timetabletab/TimetableTuitionClassSlot.java b/src/main/java/seedu/times/ui/timetabletab/TimetableTuitionClassSlot.java new file mode 100644 index 00000000000..c6f3df265cf --- /dev/null +++ b/src/main/java/seedu/times/ui/timetabletab/TimetableTuitionClassSlot.java @@ -0,0 +1,34 @@ +package seedu.times.ui.timetabletab; + +import java.time.temporal.ChronoUnit; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.times.model.tuitionclass.TuitionClass; + +// Solution below adapted from +// https://github.com/AY1920S2-CS2103-W15-4/main/blob/master/src/main/java/clzzz/helper/ui/calendar/CalendarSlot.java +/** + * Ui for the timetable tuition class slot. + */ +public class TimetableTuitionClassSlot extends TimetableRegion { + + private static final String FXML = "timetableTab/TimetableTuitionClassSlot.fxml"; + + @FXML + private Label time; + + @FXML + private Label className; + + /** + * Creates a timetable tuition class slot on the Timetable Ui. + * + * @param tuitionClass tuitionClass to be built into the Timetable Ui + */ + public TimetableTuitionClassSlot(TuitionClass tuitionClass) { + super(FXML, tuitionClass.getStartTime().until(tuitionClass.getEndTime(), ChronoUnit.MINUTES)); + className.setText(tuitionClass.getClassNameString()); + time.setText(tuitionClass.getClassTiming().getClassTiming()); + } +} diff --git a/src/main/resources/images/TimestableLogo.png b/src/main/resources/images/TimestableLogo.png new file mode 100644 index 00000000000..98f6b108dcc Binary files /dev/null and b/src/main/resources/images/TimestableLogo.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..9e0394f4e11 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,7 @@ - + diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index c9a38f2b105..b567265a119 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -11,7 +11,7 @@ - + @@ -20,9 +20,9 @@ - + - + minHeight="200" prefHeight="200" maxHeight="150"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/RedTheme.css similarity index 66% rename from src/main/resources/view/DarkTheme.css rename to src/main/resources/view/RedTheme.css index 9ce9bcfb569..4fbdfd85a10 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/RedTheme.css @@ -1,6 +1,12 @@ +/* Colors */ +/* main-color: #722F37;*/ +/* secondary-color: #7e4147;*/ +/* third-color: #86565d */ +/* selected-color: #7a2a33 */ + .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: derive(#7e4147, 20%); + background-color: #7e4147; /* Used in the default.html file */ } .label { @@ -13,14 +19,14 @@ .label-bright { -fx-font-size: 11pt; -fx-font-family: "Open Sans Semibold"; - -fx-text-fill: white; + -fx-text-fill: #484848; -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; -fx-font-family: "Open Sans Light"; - -fx-text-fill: white; + -fx-text-fill: #797979; -fx-opacity: 1; } @@ -30,6 +36,7 @@ } .tab-pane { + -fx-font-family: "Open Sans Semibold"; -fx-padding: 0 0 0 1; } @@ -40,9 +47,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: #722F37; + -fx-control-inner-background: #722F37; + -fx-background-color: #722F37; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -67,7 +74,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; -fx-font-family: "Open Sans Light"; - -fx-text-fill: white; + -fx-text-fill: #7e4147; -fx-alignment: center-left; -fx-opacity: 1; } @@ -77,42 +84,43 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-background-color: derive(#7e4147, 20%); + -fx-border-color: transparent transparent transparent #86565d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#7e4147, 20%); } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#7e4147, 20%); } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; + -fx-background-color: derive(#7e4147, 20%); } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #7e4147; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #86565d; } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #7a2a33; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: #7a2a33; -fx-border-width: 1; } @@ -133,21 +141,25 @@ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#7e4147, 20%); +} + +.command-box { + -fx-background-color: #7e4147; } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: derive(#722F37, 20%); + -fx-border-color: derive(#7e4147, 10%); -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#722F37, 30%); } .result-display { - -fx-background-color: transparent; + -fx-background-color: derive(#722F37, 30%) !important; -fx-font-family: "Open Sans Light"; -fx-font-size: 13pt; -fx-text-fill: white; @@ -165,8 +177,8 @@ } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(#722F37, 30%); + -fx-border-color: derive(#7e4147, 25%); -fx-border-width: 1px; } @@ -175,17 +187,17 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#7e4147, 30%); + -fx-border-color: derive(#722F37, 30%); -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#722F37, 30%); } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#722F37, 50%); } .context-menu .label { @@ -193,7 +205,7 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#722F37, 20%); } .menu-bar .label { @@ -204,7 +216,15 @@ } .menu .left-container { - -fx-background-color: black; + -fx-background-color: #722F37; +} + +.menu-button:hover, .menu-button:focused, .menu-button:showing { + -fx-background-color: #722F37; +} + +.menu-item:focused { + -fx-background-color: #722F37; } /* @@ -217,7 +237,7 @@ -fx-border-color: #e2e2e2; -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; + -fx-background-color: #722F37; -fx-font-family: "Open Sans Regular"; -fx-font-size: 11pt; -fx-text-fill: #d8d8d8; @@ -230,7 +250,7 @@ .button:pressed, .button:default:hover:pressed { -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-text-fill: #722F37; } .button:focused { @@ -243,7 +263,7 @@ .button:disabled, .button:default:disabled { -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; + -fx-background-color: #722F37; -fx-text-fill: white; } @@ -257,11 +277,11 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: #722F37; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: #722F37; } .dialog-pane > *.label.content { @@ -271,7 +291,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(#722F37, 25%); } .dialog-pane:header *.header-panel *.label { @@ -282,11 +302,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#722F37, 20%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#722F37, 50%); -fx-background-insets: 3; } @@ -318,7 +338,7 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: #722F37; -fx-background-insets: 0; -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; @@ -328,15 +348,25 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #personListPanel, #personWebpage #timetablePanel { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: derive(#722F37, 10%); -fx-background-radius: 0; } +.tab-header-background{ + -fx-background-color: #383838; + -fx-background-radius: 0; +} + +.tab-label { + -fx-text-fill: white; +} + + #tags { -fx-hgap: 7; -fx-vgap: 3; @@ -344,9 +374,36 @@ #tags .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #a85862; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; -fx-font-size: 11; } + +/* For tab css */ +.tab-header-background { + -fx-opacity: 0; +} + +.tab-pane { + -fx-tab-min-width: 90px; +} + +.tab { + -fx-background-color: #a85862; + -fx-pref-height: 35; +} + +.tab:selected { + -fx-background-color: #b74159; +} + +.tab:hover { + -fx-background-color: #c27882; +} + +/* Reused from https://stackoverflow.com/a/34247033/12499338 */ +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-border-color: transparent; +} diff --git a/src/main/resources/view/StudentListCard.fxml b/src/main/resources/view/StudentListCard.fxml new file mode 100644 index 00000000000..665876e0aaa --- /dev/null +++ b/src/main/resources/view/StudentListCard.fxml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/StudentListPanel.fxml similarity index 77% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/StudentListPanel.fxml index 8836d323cc5..e6b906fed69 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/StudentListPanel.fxml @@ -4,5 +4,5 @@ - + diff --git a/src/main/resources/view/classTab/ClassPanel.css b/src/main/resources/view/classTab/ClassPanel.css new file mode 100644 index 00000000000..0936e6581a5 --- /dev/null +++ b/src/main/resources/view/classTab/ClassPanel.css @@ -0,0 +1,6 @@ +.studentLabel, .classLabel { + -fx-text-fill: white; + -fx-padding: 15; + -fx-font-size: 25; + -fx-label-padding: 0 10 0 0; +} diff --git a/src/main/resources/view/classTab/ClassPanel.fxml b/src/main/resources/view/classTab/ClassPanel.fxml new file mode 100644 index 00000000000..37776ba32e9 --- /dev/null +++ b/src/main/resources/view/classTab/ClassPanel.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/classTab/StudentCardClassTab.fxml b/src/main/resources/view/classTab/StudentCardClassTab.fxml new file mode 100644 index 00000000000..665876e0aaa --- /dev/null +++ b/src/main/resources/view/classTab/StudentCardClassTab.fxml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/classTab/StudentClassListPanel.fxml b/src/main/resources/view/classTab/StudentClassListPanel.fxml new file mode 100644 index 00000000000..b15fe39a400 --- /dev/null +++ b/src/main/resources/view/classTab/StudentClassListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/classTab/StudentListCard.fxml similarity index 100% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/classTab/StudentListCard.fxml diff --git a/src/main/resources/view/classTab/StudentListPanel.fxml b/src/main/resources/view/classTab/StudentListPanel.fxml new file mode 100644 index 00000000000..e6b906fed69 --- /dev/null +++ b/src/main/resources/view/classTab/StudentListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/classTab/TuitionClassListCard.fxml b/src/main/resources/view/classTab/TuitionClassListCard.fxml new file mode 100644 index 00000000000..19af967bacb --- /dev/null +++ b/src/main/resources/view/classTab/TuitionClassListCard.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/classTab/TuitionClassListPanel.fxml b/src/main/resources/view/classTab/TuitionClassListPanel.fxml new file mode 100644 index 00000000000..b00ccfa48c4 --- /dev/null +++ b/src/main/resources/view/classTab/TuitionClassListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/timetableTab/Timetable.css b/src/main/resources/view/timetableTab/Timetable.css new file mode 100644 index 00000000000..37673d60238 --- /dev/null +++ b/src/main/resources/view/timetableTab/Timetable.css @@ -0,0 +1,43 @@ +.timetable-day { + -fx-font-family: "Open Sans Semibold"; + -fx-font-size: 16px; + -fx-text-fill: white; + -fx-padding: 25; +} + +.timetable-day-box { + -fx-font-family: "Open Sans Semibold"; + -fx-background-color: derive(#7e4147, 20%); + -fx-border-color: #C0888E; +} + +.timetable-scroll { + -fx-fit-to-height: true; + -fx-max-width: 3750; +} + +.timetable-class-slot-box { + -fx-background-color: #C26565; + -fx-padding: 15; + -fx-border-color: #C6949A; +} + +.timetable-time-panel { + -fx-background-color: #BA7C83; + -fx-padding: 10; + -fx-max-height: 1; + -fx-text-alignment: right; + -fx-border-color: #C6949A; +} + +.timetable-label { + -fx-text-fill: white; + -fx-text-alignment: center; +} + +.timetable-empty-slot-box { + -fx-background-color: derive(#7e4147, 20%); + -fx-background-insets: 5 0 5 0; + -fx-background-radius: 10; + -fx-padding: 10; +} diff --git a/src/main/resources/view/timetableTab/TimetableDay.fxml b/src/main/resources/view/timetableTab/TimetableDay.fxml new file mode 100644 index 00000000000..6583f2db8e1 --- /dev/null +++ b/src/main/resources/view/timetableTab/TimetableDay.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/main/resources/view/timetableTab/TimetableEmptySlot.fxml b/src/main/resources/view/timetableTab/TimetableEmptySlot.fxml new file mode 100644 index 00000000000..9be4f5b073d --- /dev/null +++ b/src/main/resources/view/timetableTab/TimetableEmptySlot.fxml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/view/timetableTab/TimetableHeader.fxml b/src/main/resources/view/timetableTab/TimetableHeader.fxml new file mode 100644 index 00000000000..808dc94673a --- /dev/null +++ b/src/main/resources/view/timetableTab/TimetableHeader.fxml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/view/timetableTab/TimetablePanel.fxml b/src/main/resources/view/timetableTab/TimetablePanel.fxml new file mode 100644 index 00000000000..b1705dc2770 --- /dev/null +++ b/src/main/resources/view/timetableTab/TimetablePanel.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/view/timetableTab/TimetableTuitionClassSlot.fxml b/src/main/resources/view/timetableTab/TimetableTuitionClassSlot.fxml new file mode 100644 index 00000000000..6b8fc206366 --- /dev/null +++ b/src/main/resources/view/timetableTab/TimetableTuitionClassSlot.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json deleted file mode 100644 index 48831cc7674..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "persons": [ { - "name": "Alice Pauline", - "phone": "94351253", - "email": "alice@example.com", - "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] - }, { - "name": "Alice Pauline", - "phone": "94351253", - "email": "pauline@example.com", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json deleted file mode 100644 index ad3f135ae42..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "persons": [ { - "name": "Hans Muster", - "phone": "9482424", - "email": "invalid@email!3e", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json deleted file mode 100644 index f10eddee12e..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", - "persons" : [ { - "name" : "Alice Pauline", - "phone" : "94351253", - "email" : "alice@example.com", - "address" : "123, Jurong West Ave 6, #08-111", - "tagged" : [ "friends" ] - }, { - "name" : "Benson Meier", - "phone" : "98765432", - "email" : "johnd@example.com", - "address" : "311, Clementi Ave 2, #02-25", - "tagged" : [ "owesMoney", "friends" ] - }, { - "name" : "Carl Kurz", - "phone" : "95352563", - "email" : "heinz@example.com", - "address" : "wall street", - "tagged" : [ ] - }, { - "name" : "Daniel Meier", - "phone" : "87652533", - "email" : "cornelia@example.com", - "address" : "10th street", - "tagged" : [ "friends" ] - }, { - "name" : "Elle Meyer", - "phone" : "9482224", - "email" : "werner@example.com", - "address" : "michegan ave", - "tagged" : [ ] - }, { - "name" : "Fiona Kunz", - "phone" : "9482427", - "email" : "lydia@example.com", - "address" : "little tokyo", - "tagged" : [ ] - }, { - "name" : "George Best", - "phone" : "9482442", - "email" : "anna@example.com", - "address" : "4th street", - "tagged" : [ ] - } ] -} diff --git a/src/test/data/JsonSerializableTimesTableTest/duplicatePersonTimesTable.json b/src/test/data/JsonSerializableTimesTableTest/duplicatePersonTimesTable.json new file mode 100644 index 00000000000..4b5400033dd --- /dev/null +++ b/src/test/data/JsonSerializableTimesTableTest/duplicatePersonTimesTable.json @@ -0,0 +1,27 @@ +{ + "persons": [ { + "name": "Alice Pauline", + "phone": "94351253", + "email": "alice@example.com", + "address": "123, Jurong West Ave 6, #08-111", + "nok" : { + "name" : "Jack Doe", + "phone" : "10987654", + "email" : "jackd@example.com", + "address" : "311, Clementi Ave 2, #02-25" + }, + "tagged": [ "friends" ] + }, { + "name": "Alice Pauline", + "phone": "94351253", + "email": "pauline@example.com", + "address": "4th street", + "nok" : { + "name" : "Jack Doe", + "phone" : "10987654", + "email" : "jackd@example.com", + "address" : "311, Clementi Ave 2, #02-25" + } + } ], + "classes" : [] +} diff --git a/src/test/data/JsonSerializableTimesTableTest/invalidPersonTimesTable.json b/src/test/data/JsonSerializableTimesTableTest/invalidPersonTimesTable.json new file mode 100644 index 00000000000..08efc458eb4 --- /dev/null +++ b/src/test/data/JsonSerializableTimesTableTest/invalidPersonTimesTable.json @@ -0,0 +1,15 @@ +{ + "persons": [ { + "name": "Hans Muster", + "phone": "9482424", + "email": "invalid@email!3e", + "address": "4th street", + "nok" : { + "name" : "Jack Doe", + "phone" : "10987654", + "email" : "jackd@example.com", + "address" : "311, Clementi Ave 2, #02-25" + } + } ], + "classes" : [ ] +} diff --git a/src/test/data/JsonSerializableTimesTableTest/typicalPersonsTimesTable.json b/src/test/data/JsonSerializableTimesTableTest/typicalPersonsTimesTable.json new file mode 100644 index 00000000000..7af03be6d72 --- /dev/null +++ b/src/test/data/JsonSerializableTimesTableTest/typicalPersonsTimesTable.json @@ -0,0 +1,125 @@ +{ + "_comment": "TimesTable save file which contains the same Person values as in TypicalPersons#getTypicalTimesTable()", + "persons" : [ { + "name" : "Alice Pauline", + "phone" : "94351253", + "email" : "alice@example.com", + "address" : "123, Jurong West Ave 6, #08-111", + "nok" : { + "name" : "Long Chai Boon", + "phone" : "67785914", + "email" : "longchatbooon@gmail.com", + "address" : "325, Clementi State 3, #40-32" + }, + "tagged" : [ "Maths" ] + }, { + "name" : "Benson Meier", + "phone" : "98765432", + "email" : "johnd@example.com", + "address" : "311, Clementi Ave 2, #02-25", + "nok" : { + "name" : "Short Chai Boon", + "phone" : "87759868", + "email" : "hahiihi@gmail.com", + "address" : "325, West State 3, #60-12" + }, + "tagged" : [ "Physics" ] + }, { + "name" : "Carl Kurz", + "phone" : "95352563", + "email" : "heinz@example.com", + "address" : "Campbell Road Ave 2, #11", + "nok" : { + "name" : "Jamie Kurz", + "phone" : "62212222", + "email" : "dontcallmeillcome@gmail.com", + "address" : "325, Bat Cave 3, #10-23" + }, + "tagged" : [ ] + }, { + "name" : "Daniel Meier", + "phone" : "87652533", + "email" : "cornelia@example.com", + "address" : "10th street", + "nok" : { + "name" : "Bad Chai Boon", + "phone" : "88888888", + "email" : "happyorsadyouchoose@gmail.com", + "address" : "1, Happy Sad 3, #10-12" + }, + "tagged" : [ "Maths" ] + }, { + "name" : "Elle Meyer", + "phone" : "9482224", + "email" : "werner@example.com", + "address" : "michegan ave", + "nok" : { + "name" : "Bernard Wan", + "phone" : "67785314", + "email" : "bernard@gmail.com", + "address" : "222, Berkeys State 4, #11-32" + }, + "tagged" : [ ] + }, { + "name" : "Fiona Kunz", + "phone" : "9482427", + "email" : "lydia@example.com", + "address" : "little tokyo", + "nok" : { + "name" : "Ong Lin Zhen", + "phone" : "67382344", + "email" : "zhenngggii@gmail.com", + "address" : "323, Lin Estate 3, #44-31" + }, + "tagged" : [ ] + }, { + "name" : "George Best", + "phone" : "9482442", + "email" : "anna@example.com", + "address" : "4th street", + "nok" : { + "name" : "Hehe Chai Red", + "phone" : "67111111", + "email" : "salmon@gmail.com", + "address" : "999, Estate State 4, #10-32" + }, + "tagged" : [ ] + } ], + "classes" : [{ + "classTiming" : "MON 10:00-12:00", + "className" : "JC Physics", + "rate" : "70", + "location" : "Jaycee Tuition Center Nex", + "students" : [ "Benson Meier", "Alice Pauline" ] + }, { + "classTiming" : "Tue 11:00-13:00", + "className" : "Sec 4 Physics", + "rate" : "77", + "location" : "Learning Lab Orchard", + "students" : [ "Carl Kurz", "Daniel Meier" ] + }, { + "classTiming" : "Wed 15:00-17:00", + "className" : "JC Maths", + "rate" : "55", + "location" : "Bukit Merah Block 614 #01-330", + "students" : [ ] + }, { + "classTiming" : "Thu 10:00-12:00", + "className" : "Sec 4 Maths", + "rate" : "60", + "location" : "Merlion Tuition Center Kovan", + "students" : [ ] + }, { + "classTiming" : "Fri 15:00-18:00", + "className" : "JC Chemistry", + "rate" : "50", + "location" : "Hougang Blk 313 #11-394", + "students" : [ ] + }, { + "classTiming" : "Sat 13:00-16:00", + "className" : "Sec 4 Chemistry", + "rate" : "80", + "location" : "Kumon at Orchard", + "students" : [ ] + }] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonTimesTableStorageTest/invalidAndValidPersonTimesTable.json similarity index 52% rename from src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json rename to src/test/data/JsonTimesTableStorageTest/invalidAndValidPersonTimesTable.json index 6a4d2b7181c..123e54fc35c 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonTimesTableStorageTest/invalidAndValidPersonTimesTable.json @@ -3,11 +3,17 @@ "name": "Valid Person", "phone": "9482424", "email": "hans@example.com", - "address": "4th street" + "address": "4th street", + "rate": "70", + "classTiming": "23:59", + "location": "5th street" }, { "name": "Person With Invalid Phone Field", "phone": "948asdf2424", "email": "hans@example.com", - "address": "4th street" + "address": "4th street", + "rate": "70", + "classTiming": "23:59", + "location": "5th street" } ] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonTimesTableStorageTest/invalidPersonTimesTable.json similarity index 58% rename from src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json rename to src/test/data/JsonTimesTableStorageTest/invalidPersonTimesTable.json index ccd21f7d1a9..ea8340e8f77 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonTimesTableStorageTest/invalidPersonTimesTable.json @@ -3,6 +3,9 @@ "name": "Person with invalid name field: Ha!ns Mu@ster", "phone": "9482424", "email": "hans@example.com", - "address": "4th street" + "address": "4th street", + "rate": "70", + "classTiming": "23:59", + "location": "4th street" } ] } diff --git a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json b/src/test/data/JsonTimesTableStorageTest/notJsonFormatTimesTable.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json rename to src/test/data/JsonTimesTableStorageTest/notJsonFormatTimesTable.json diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 1037548a9cd..5ce9a6e00aa 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,5 +9,5 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.json" + "timesTableFilePath" : "timestable.json" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index b819bed900a..634943d14ac 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,5 +7,5 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.json" + "timesTableFilePath" : "timestable.json" } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java deleted file mode 100644 index cb8714bb055..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) for {@code AddCommand}. - */ -public class AddCommandIntegrationTest { - - private Model model; - - @BeforeEach - public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - } - - @Test - public void execute_newPerson_success() { - Person validPerson = new PersonBuilder().build(); - - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.addPerson(validPerson); - - assertCommandSuccess(new AddCommand(validPerson), model, - String.format(AddCommand.MESSAGE_SUCCESS, validPerson), expectedModel); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person personInList = model.getAddressBook().getPersonList().get(0); - assertCommandFailure(new AddCommand(personInList), model, AddCommand.MESSAGE_DUPLICATE_PERSON); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java deleted file mode 100644 index 5865713d5dd..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandTest { - - @Test - public void constructor_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new AddCommand(null)); - } - - @Test - public void execute_personAcceptedByModel_addSuccessful() throws Exception { - ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); - Person validPerson = new PersonBuilder().build(); - - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); - assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person validPerson = new PersonBuilder().build(); - AddCommand addCommand = new AddCommand(validPerson); - ModelStub modelStub = new ModelStubWithPerson(validPerson); - - assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); - } - - @Test - public void equals() { - Person alice = new PersonBuilder().withName("Alice").build(); - Person bob = new PersonBuilder().withName("Bob").build(); - AddCommand addAliceCommand = new AddCommand(alice); - AddCommand addBobCommand = new AddCommand(bob); - - // same object -> returns true - assertTrue(addAliceCommand.equals(addAliceCommand)); - - // same values -> returns true - AddCommand addAliceCommandCopy = new AddCommand(alice); - assertTrue(addAliceCommand.equals(addAliceCommandCopy)); - - // different types -> returns false - assertFalse(addAliceCommand.equals(1)); - - // null -> returns false - assertFalse(addAliceCommand.equals(null)); - - // different person -> returns false - assertFalse(addAliceCommand.equals(addBobCommand)); - } - - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - throw new AssertionError("This method should not be called."); - } - - @Override - public GuiSettings getGuiSettings() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - throw new AssertionError("This method should not be called."); - } - - @Override - public Path getAddressBookFilePath() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void addPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBook(ReadOnlyAddressBook newData) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - throw new AssertionError("This method should not be called."); - } - - @Override - public boolean hasPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void deletePerson(Person target) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ObservableList getFilteredPersonList() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - throw new AssertionError("This method should not be called."); - } - } - - /** - * A Model stub that contains a single person. - */ - private class ModelStubWithPerson extends ModelStub { - private final Person person; - - ModelStubWithPerson(Person person) { - requireNonNull(person); - this.person = person; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return this.person.isSamePerson(person); - } - } - - /** - * A Model stub that always accept the person being added. - */ - private class ModelStubAcceptingPersonAdded extends ModelStub { - final ArrayList personsAdded = new ArrayList<>(); - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return personsAdded.stream().anyMatch(person::isSamePerson); - } - - @Override - public void addPerson(Person person) { - requireNonNull(person); - personsAdded.add(person); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); - } - } - -} diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java deleted file mode 100644 index 80d9110c03a..00000000000 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -public class ClearCommandTest { - - @Test - public void execute_emptyAddressBook_success() { - Model model = new ModelManager(); - Model expectedModel = new ModelManager(); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel.setAddressBook(new AddressBook()); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java deleted file mode 100644 index 643a1d08069..00000000000 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ /dev/null @@ -1,128 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.testutil.Assert.assertThrows; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; - -/** - * Contains helper methods for testing commands. - */ -public class CommandTestUtil { - - public static final String VALID_NAME_AMY = "Amy Bee"; - public static final String VALID_NAME_BOB = "Bob Choo"; - public static final String VALID_PHONE_AMY = "11111111"; - public static final String VALID_PHONE_BOB = "22222222"; - public static final String VALID_EMAIL_AMY = "amy@example.com"; - public static final String VALID_EMAIL_BOB = "bob@example.com"; - public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; - public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; - public static final String VALID_TAG_HUSBAND = "husband"; - public static final String VALID_TAG_FRIEND = "friend"; - - public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; - public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; - public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; - public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB; - public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY; - public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; - public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; - public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; - public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; - public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; - - public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names - public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones - public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol - public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses - public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags - - public static final String PREAMBLE_WHITESPACE = "\t \r \n"; - public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; - - public static final EditCommand.EditPersonDescriptor DESC_AMY; - public static final EditCommand.EditPersonDescriptor DESC_BOB; - - static { - DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_FRIEND).build(); - DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); - } - - /** - * Executes the given {@code command}, confirms that
- * - the returned {@link CommandResult} matches {@code expectedCommandResult}
- * - the {@code actualModel} matches {@code expectedModel} - */ - public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, - Model expectedModel) { - try { - CommandResult result = command.execute(actualModel); - assertEquals(expectedCommandResult, result); - assertEquals(expectedModel, actualModel); - } catch (CommandException ce) { - throw new AssertionError("Execution of command should not fail.", ce); - } - } - - /** - * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} - * that takes a string {@code expectedMessage}. - */ - public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { - CommandResult expectedCommandResult = new CommandResult(expectedMessage); - assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); - } - - /** - * Executes the given {@code command}, confirms that
- * - a {@code CommandException} is thrown
- * - the CommandException message matches {@code expectedMessage}
- * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged - */ - public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { - // we are unable to defensively copy the model for comparison later, so we can - // only do so by copying its components. - AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); - List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList()); - - assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); - assertEquals(expectedAddressBook, actualModel.getAddressBook()); - assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); - } - /** - * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the - * {@code model}'s address book. - */ - public static void showPersonAtIndex(Model model, Index targetIndex) { - assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().size()); - - Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); - final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); - - assertEquals(1, model.getFilteredPersonList().size()); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java deleted file mode 100644 index 214c6c2507b..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) and unit tests for EditCommand. - */ -public class EditCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_someFieldsSpecifiedUnfilteredList_success() { - Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); - Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); - - PersonBuilder personInList = new PersonBuilder(lastPerson); - Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); - EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(lastPerson, editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder(personInList).build()); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_invalidPersonIndexUnfilteredList_failure() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Edit filtered list where index is larger than size of filtered list, - * but smaller than size of address book - */ - @Test - public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - EditCommand editCommand = new EditCommand(outOfBoundIndex, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); - - // same values -> returns true - EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); - assertTrue(standardCommand.equals(commandWithSameValues)); - - // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); - - // null -> returns false - assertFalse(standardCommand.equals(null)); - - // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); - - // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); - - // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java deleted file mode 100644 index 9b15db28bbb..00000000000 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Contains integration tests (interaction with the Model) for {@code FindCommand}. - */ -public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); - - // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); - - // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); - - // different types -> returns false - assertFalse(findFirstCommand.equals(1)); - - // null -> returns false - assertFalse(findFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); - } - - @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); - } - - /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. - */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); - } -} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java deleted file mode 100644 index 5cf487d7ebb..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); - - @Test - public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); - - // whitespace only preamble - assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple names - last name accepted - assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple phones - last phone accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple emails - last email accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple addresses - last address accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); - } - - @Test - public void parse_optionalFieldsMissing_success() { - // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); - } - - @Test - public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - - // missing name prefix - assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - - // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); - } - - @Test - public void parse_invalidValue_failure() { - // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); - - // invalid phone - assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS); - - // invalid email - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); - - // invalid address - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS); - - // invalid tag - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); - - // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, - Name.MESSAGE_CONSTRAINTS); - - // non-empty preamble - assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } -} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java deleted file mode 100644 index 70f4f0e79c4..00000000000 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -public class FindCommandParserTest { - - private FindCommandParser parser = new FindCommandParser(); - - @Test - public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); - } - -} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java deleted file mode 100644 index 87782528ecd..00000000000 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package seedu.address.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.testutil.PersonBuilder; - -public class AddressBookTest { - - private final AddressBook addressBook = new AddressBook(); - - @Test - public void constructor() { - assertEquals(Collections.emptyList(), addressBook.getPersonList()); - } - - @Test - public void resetData_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.resetData(null)); - } - - @Test - public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); - addressBook.resetData(newData); - assertEquals(newData, addressBook); - } - - @Test - public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { - // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); - - assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); - } - - @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); - } - - @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(addressBook.hasPerson(editedAlice)); - } - - @Test - public void getPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); - } - - /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. - */ - private static class AddressBookStub implements ReadOnlyAddressBook { - private final ObservableList persons = FXCollections.observableArrayList(); - - AddressBookStub(Collection persons) { - this.persons.setAll(persons); - } - - @Override - public ObservableList getPersonList() { - return persons; - } - } - -} diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java deleted file mode 100644 index 64d07d79ee2..00000000000 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.model.tag; - -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class TagTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Tag(null)); - } - - @Test - public void constructor_invalidTagName_throwsIllegalArgumentException() { - String invalidTagName = ""; - assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName)); - } - - @Test - public void isValidTagName() { - // null tag name - assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); - } - -} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java deleted file mode 100644 index ac3c3af9566..00000000000 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; - -public class JsonAddressBookStorageTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); - - @TempDir - public Path testFolder; - - @Test - public void readAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> readAddressBook(null)); - } - - private java.util.Optional readAddressBook(String filePath) throws Exception { - return new JsonAddressBookStorage(Paths.get(filePath)).readAddressBook(addToTestDataPathIfNotNull(filePath)); - } - - private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { - return prefsFileInTestDataFolder != null - ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) - : null; - } - - @Test - public void read_missingFile_emptyResult() throws Exception { - assertFalse(readAddressBook("NonExistentFile.json").isPresent()); - } - - @Test - public void read_notJsonFormat_exceptionThrown() { - assertThrows(DataConversionException.class, () -> readAddressBook("notJsonFormatAddressBook.json")); - } - - @Test - public void readAddressBook_invalidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidPersonAddressBook.json")); - } - - @Test - public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidPersonAddressBook.json")); - } - - @Test - public void readAndSaveAddressBook_allInOrder_success() throws Exception { - Path filePath = testFolder.resolve("TempAddressBook.json"); - AddressBook original = getTypicalAddressBook(); - JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath); - - // Save in new file and read back - jsonAddressBookStorage.saveAddressBook(original, filePath); - ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Modify data, overwrite exiting file, and read back - original.addPerson(HOON); - original.removePerson(ALICE); - jsonAddressBookStorage.saveAddressBook(original, filePath); - readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Save and read without specifying file path - original.addPerson(IDA); - jsonAddressBookStorage.saveAddressBook(original); // file path not specified - readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified - assertEquals(original, new AddressBook(readBack)); - - } - - @Test - public void saveAddressBook_nullAddressBook_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(null, "SomeFile.json")); - } - - /** - * Saves {@code addressBook} at the specified {@code filePath}. - */ - private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { - try { - new JsonAddressBookStorage(Paths.get(filePath)) - .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); - } catch (IOException ioe) { - throw new AssertionError("There should not be an error writing to the file.", ioe); - } - } - - @Test - public void saveAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null)); - } -} diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java deleted file mode 100644 index 188c9058d20..00000000000 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.testutil.Assert.assertThrows; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.AddressBook; -import seedu.address.testutil.TypicalPersons; - -public class JsonSerializableAddressBookTest { - - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); - private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); - private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); - private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); - - @Test - public void toModelType_typicalPersonsFile_success() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, - JsonSerializableAddressBook.class).get(); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); - assertEquals(addressBookFromFile, typicalPersonsAddressBook); - } - - @Test - public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, dataFromFile::toModelType); - } - - @Test - public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, - dataFromFile::toModelType); - } - -} diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java deleted file mode 100644 index d53799fd110..00000000000 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class to help with building Addressbook objects. - * Example usage:
- * {@code AddressBook ab = new AddressBookBuilder().withPerson("John", "Doe").build();} - */ -public class AddressBookBuilder { - - private AddressBook addressBook; - - public AddressBookBuilder() { - addressBook = new AddressBook(); - } - - public AddressBookBuilder(AddressBook addressBook) { - this.addressBook = addressBook; - } - - /** - * Adds a new {@code Person} to the {@code AddressBook} that we are building. - */ - public AddressBookBuilder withPerson(Person person) { - addressBook.addPerson(person); - return this; - } - - public AddressBook build() { - return addressBook; - } -} diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java deleted file mode 100644 index 4584bd5044e..00000000000 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.testutil; - -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * A utility class to help with building EditPersonDescriptor objects. - */ -public class EditPersonDescriptorBuilder { - - private EditPersonDescriptor descriptor; - - public EditPersonDescriptorBuilder() { - descriptor = new EditPersonDescriptor(); - } - - public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) { - this.descriptor = new EditPersonDescriptor(descriptor); - } - - /** - * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details - */ - public EditPersonDescriptorBuilder(Person person) { - descriptor = new EditPersonDescriptor(); - descriptor.setName(person.getName()); - descriptor.setPhone(person.getPhone()); - descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); - } - - /** - * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withName(String name) { - descriptor.setName(new Name(name)); - return this; - } - - /** - * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withPhone(String phone) { - descriptor.setPhone(new Phone(phone)); - return this; - } - - /** - * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withEmail(String email) { - descriptor.setEmail(new Email(email)); - return this; - } - - /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} - * that we are building. - */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); - return this; - } - - public EditPersonDescriptor build() { - return descriptor; - } -} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java deleted file mode 100644 index 6be381d39ba..00000000000 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ /dev/null @@ -1,96 +0,0 @@ -package seedu.address.testutil; - -import java.util.HashSet; -import java.util.Set; - -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.model.util.SampleDataUtil; - -/** - * A utility class to help with building Person objects. - */ -public class PersonBuilder { - - public static final String DEFAULT_NAME = "Amy Bee"; - public static final String DEFAULT_PHONE = "85355255"; - public static final String DEFAULT_EMAIL = "amy@gmail.com"; - public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; - - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - /** - * Creates a {@code PersonBuilder} with the default details. - */ - public PersonBuilder() { - name = new Name(DEFAULT_NAME); - phone = new Phone(DEFAULT_PHONE); - email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); - } - - /** - * Initializes the PersonBuilder with the data of {@code personToCopy}. - */ - public PersonBuilder(Person personToCopy) { - name = personToCopy.getName(); - phone = personToCopy.getPhone(); - email = personToCopy.getEmail(); - address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); - } - - /** - * Sets the {@code Name} of the {@code Person} that we are building. - */ - public PersonBuilder withName(String name) { - this.name = new Name(name); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. - */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); - return this; - } - - /** - * Sets the {@code Address} of the {@code Person} that we are building. - */ - public PersonBuilder withAddress(String address) { - this.address = new Address(address); - return this; - } - - /** - * Sets the {@code Phone} of the {@code Person} that we are building. - */ - public PersonBuilder withPhone(String phone) { - this.phone = new Phone(phone); - return this; - } - - /** - * Sets the {@code Email} of the {@code Person} that we are building. - */ - public PersonBuilder withEmail(String email) { - this.email = new Email(email); - return this; - } - - public Person build() { - return new Person(name, phone, email, address, tags); - } - -} diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java deleted file mode 100644 index 90849945183..00000000000 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Person; -import seedu.address.model.tag.Tag; - -/** - * A utility class for Person. - */ -public class PersonUtil { - - /** - * Returns an add command string for adding the {@code person}. - */ - public static String getAddCommand(Person person) { - return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); - } - - /** - * Returns the part of command string for the given {@code person}'s details. - */ - public static String getPersonDetails(Person person) { - StringBuilder sb = new StringBuilder(); - sb.append(PREFIX_NAME + person.getName().fullName + " "); - sb.append(PREFIX_PHONE + person.getPhone().value + " "); - sb.append(PREFIX_EMAIL + person.getEmail().value + " "); - sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") - ); - return sb.toString(); - } - - /** - * Returns the part of command string for the given {@code EditPersonDescriptor}'s details. - */ - public static String getEditPersonDescriptorDetails(EditPersonDescriptor descriptor) { - StringBuilder sb = new StringBuilder(); - descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); - descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); - descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); - descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); - if (descriptor.getTags().isPresent()) { - Set tags = descriptor.getTags().get(); - if (tags.isEmpty()) { - sb.append(PREFIX_TAG); - } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); - } - } - return sb.toString(); - } -} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java deleted file mode 100644 index 1e613937657..00000000000 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.commons.core.index.Index; - -/** - * A utility class containing a list of {@code Index} objects to be used in tests. - */ -public class TypicalIndexes { - public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); - public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); - public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); -} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java deleted file mode 100644 index fec76fb7129..00000000000 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class containing a list of {@code Person} objects to be used in tests. - */ -public class TypicalPersons { - - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); - public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); - public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); - - // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); - - // Manually added - Person's details found in {@code CommandTestUtil} - public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); - - public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - - private TypicalPersons() {} // prevents instantiation - - /** - * Returns an {@code AddressBook} with all the typical persons. - */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); - for (Person person : getTypicalPersons()) { - ab.addPerson(person); - } - return ab; - } - - public static List getTypicalPersons() { - return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); - } -} diff --git a/src/test/java/seedu/address/AppParametersTest.java b/src/test/java/seedu/times/AppParametersTest.java similarity index 98% rename from src/test/java/seedu/address/AppParametersTest.java rename to src/test/java/seedu/times/AppParametersTest.java index 61326b2d31a..5c6e962c906 100644 --- a/src/test/java/seedu/address/AppParametersTest.java +++ b/src/test/java/seedu/times/AppParametersTest.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.times; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/times/commons/core/ConfigTest.java similarity index 95% rename from src/test/java/seedu/address/commons/core/ConfigTest.java rename to src/test/java/seedu/times/commons/core/ConfigTest.java index 07cd7f73d53..0453a6b6e6e 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/times/commons/core/ConfigTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/seedu/times/commons/core/VersionTest.java similarity index 98% rename from src/test/java/seedu/address/commons/core/VersionTest.java rename to src/test/java/seedu/times/commons/core/VersionTest.java index 495cd231554..f5588d6da7c 100644 --- a/src/test/java/seedu/address/commons/core/VersionTest.java +++ b/src/test/java/seedu/times/commons/core/VersionTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.core; +package seedu.times.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/core/index/IndexTest.java b/src/test/java/seedu/times/commons/core/index/IndexTest.java similarity index 95% rename from src/test/java/seedu/address/commons/core/index/IndexTest.java rename to src/test/java/seedu/times/commons/core/index/IndexTest.java index a3ec6f8e747..949e66e7688 100644 --- a/src/test/java/seedu/address/commons/core/index/IndexTest.java +++ b/src/test/java/seedu/times/commons/core/index/IndexTest.java @@ -1,9 +1,9 @@ -package seedu.address.commons.core.index; +package seedu.times.commons.core.index; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/times/commons/util/AppUtilTest.java similarity index 91% rename from src/test/java/seedu/address/commons/util/AppUtilTest.java rename to src/test/java/seedu/times/commons/util/AppUtilTest.java index 594de1e6365..6eae422aa74 100644 --- a/src/test/java/seedu/address/commons/util/AppUtilTest.java +++ b/src/test/java/seedu/times/commons/util/AppUtilTest.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java b/src/test/java/seedu/times/commons/util/CollectionUtilTest.java similarity index 96% rename from src/test/java/seedu/address/commons/util/CollectionUtilTest.java rename to src/test/java/seedu/times/commons/util/CollectionUtilTest.java index b467a3dc025..4e86120e514 100644 --- a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java +++ b/src/test/java/seedu/times/commons/util/CollectionUtilTest.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.times.testutil.Assert.assertThrows; import java.util.Arrays; import java.util.Collection; diff --git a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java b/src/test/java/seedu/times/commons/util/ConfigUtilTest.java similarity index 94% rename from src/test/java/seedu/address/commons/util/ConfigUtilTest.java rename to src/test/java/seedu/times/commons/util/ConfigUtilTest.java index d2ab2839a52..b71a003943f 100644 --- a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java +++ b/src/test/java/seedu/times/commons/util/ConfigUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import java.io.IOException; import java.nio.file.Path; @@ -13,8 +13,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.times.commons.core.Config; +import seedu.times.commons.exceptions.DataConversionException; public class ConfigUtilTest { diff --git a/src/test/java/seedu/address/commons/util/FileUtilTest.java b/src/test/java/seedu/times/commons/util/FileUtilTest.java similarity index 84% rename from src/test/java/seedu/address/commons/util/FileUtilTest.java rename to src/test/java/seedu/times/commons/util/FileUtilTest.java index 1fe5478c756..5f025885865 100644 --- a/src/test/java/seedu/address/commons/util/FileUtilTest.java +++ b/src/test/java/seedu/times/commons/util/FileUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/seedu/times/commons/util/JsonUtilTest.java similarity index 92% rename from src/test/java/seedu/address/commons/util/JsonUtilTest.java rename to src/test/java/seedu/times/commons/util/JsonUtilTest.java index d4907539dee..9f25d94add5 100644 --- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java +++ b/src/test/java/seedu/times/commons/util/JsonUtilTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,8 +7,8 @@ import org.junit.jupiter.api.Test; -import seedu.address.testutil.SerializableTestClass; -import seedu.address.testutil.TestUtil; +import seedu.times.testutil.SerializableTestClass; +import seedu.times.testutil.TestUtil; /** * Tests JSON Read and Write diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/times/commons/util/StringUtilTest.java similarity index 98% rename from src/test/java/seedu/address/commons/util/StringUtilTest.java rename to src/test/java/seedu/times/commons/util/StringUtilTest.java index c56d407bf3f..0493f304d75 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/times/commons/util/StringUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.times.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import java.io.FileNotFoundException; diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/times/logic/LogicManagerTest.java similarity index 57% rename from src/test/java/seedu/address/logic/LogicManagerTest.java rename to src/test/java/seedu/times/logic/LogicManagerTest.java index ad923ac249a..723226f9e74 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/times/logic/LogicManagerTest.java @@ -1,14 +1,19 @@ -package seedu.address.logic; +package seedu.times.logic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; +import static seedu.times.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PREFIX_NOK; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.AMY; import java.io.IOException; import java.nio.file.Path; @@ -17,20 +22,20 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.StorageManager; -import seedu.address.testutil.PersonBuilder; +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.ListCommand; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.UserPrefs; +import seedu.times.model.person.Student; +import seedu.times.storage.JsonTimesTableStorage; +import seedu.times.storage.JsonUserPrefsStorage; +import seedu.times.storage.StorageManager; +import seedu.times.testutil.PersonBuilder; public class LogicManagerTest { private static final IOException DUMMY_IO_EXCEPTION = new IOException("dummy exception"); @@ -43,10 +48,10 @@ public class LogicManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonTimesTableStorage timesTableStorage = + new JsonTimesTableStorage(temporaryFolder.resolve("timestable.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); - StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + StorageManager storage = new StorageManager(timesTableStorage, userPrefsStorage); logic = new LogicManager(model, storage); } @@ -59,7 +64,7 @@ public void execute_invalidCommandFormat_throwsParseException() { @Test public void execute_commandExecutionError_throwsCommandException() { String deleteCommand = "delete 9"; - assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandException(deleteCommand, MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } @Test @@ -70,27 +75,29 @@ public void execute_validCommand_success() throws Exception { @Test public void execute_storageThrowsIoException_throwsCommandException() { - // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json")); + // Setup LogicManager with JsonTimesTableIoExceptionThrowingStub + JsonTimesTableStorage timesTableStorage = + new JsonTimesTableIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionTimesTable.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); - StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + StorageManager storage = new StorageManager(timesTableStorage, userPrefsStorage); logic = new LogicManager(model, storage); // Execute add command String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY; - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + + ADDRESS_DESC_AMY + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK; + Student expectedStudent = new PersonBuilder(AMY).withTags().build(); ModelManager expectedModel = new ModelManager(); - expectedModel.addPerson(expectedPerson); + expectedModel.addStudent(expectedStudent); + //expectedModel.addTuitionClass(expectedStudent.getClassList().get(0)); String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION; assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); } @Test - public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); + public void getFilteredStudentList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredStudentList().remove(0)); } /** @@ -98,10 +105,11 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException * - no exceptions are thrown
* - the feedback message is equal to {@code expectedMessage}
* - the internal model manager state is the same as that in {@code expectedModel}
+ * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandSuccess(String inputCommand, String expectedMessage, - Model expectedModel) throws CommandException, ParseException { + Model expectedModel) throws CommandException, ParseException { CommandResult result = logic.execute(inputCommand); assertEquals(expectedMessage, result.getFeedbackToUser()); assertEquals(expectedModel, model); @@ -109,6 +117,7 @@ private void assertCommandSuccess(String inputCommand, String expectedMessage, /** * Executes the command, confirms that a ParseException is thrown and that the result message is correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertParseException(String inputCommand, String expectedMessage) { @@ -117,6 +126,7 @@ private void assertParseException(String inputCommand, String expectedMessage) { /** * Executes the command, confirms that a CommandException is thrown and that the result message is correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandException(String inputCommand, String expectedMessage) { @@ -125,11 +135,12 @@ private void assertCommandException(String inputCommand, String expectedMessage) /** * Executes the command, confirms that the exception is thrown and that the result message is correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandFailure(String inputCommand, Class expectedException, - String expectedMessage) { - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + String expectedMessage) { + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -138,10 +149,11 @@ private void assertCommandFailure(String inputCommand, Class * - the resulting error message is equal to {@code expectedMessage}
* - the internal model manager state is the same as that in {@code expectedModel}
+ * * @see #assertCommandSuccess(String, String, Model) */ private void assertCommandFailure(String inputCommand, Class expectedException, - String expectedMessage, Model expectedModel) { + String expectedMessage, Model expectedModel) { assertThrows(expectedException, expectedMessage, () -> logic.execute(inputCommand)); assertEquals(expectedModel, model); } @@ -149,13 +161,13 @@ private void assertCommandFailure(String inputCommand, Class new AddCommand(null)); + } + + @Test + public void execute_personAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Student validStudent = new PersonBuilder().build(); + + CommandResult commandResult = + new AddCommand(validStudent).execute(modelStub); + + assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validStudent), commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validStudent), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Student validStudent = new PersonBuilder().build(); + AddCommand addCommand = new AddCommand(validStudent); + ModelStub modelStub = new ModelStubWithPerson(validStudent); + + assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); + } + + @Test + public void equals() { + Student alice = new PersonBuilder().withName("Alice").build(); + Student bob = new PersonBuilder().withName("Bob").build(); + AddCommand addAliceCommand = new AddCommand(alice); + AddCommand addBobCommand = new AddCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddCommand addAliceCommandCopy = new AddCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different person -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getTimesTableFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTableFilePath(Path timesTableFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTable(ReadOnlyTimesTable newData) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyTimesTable getTimesTable() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteStudent(Student target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTuitionClass(TuitionClass target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredStudentList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredClassList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClass(TuitionClass target, TuitionClass editedClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClasses(List classes) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudents(List students) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateClassStudentLists(Name newName, Name oldName) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortStudents(Comparator c) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortClasses(Comparator c) { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single person. + */ + private class ModelStubWithPerson extends ModelStub { + private final Student student; + + ModelStubWithPerson(Student student) { + requireNonNull(student); + this.student = student; + } + + @Override + public boolean hasStudent(Student student) { + requireNonNull(student); + return this.student.isSamePerson(student); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + final ArrayList tuitionClassesAdded = new ArrayList<>(); + + @Override + public boolean hasStudent(Student student) { + requireNonNull(student); + return personsAdded.stream().anyMatch(student::isSamePerson); + } + + @Override + public void addStudent(Student student) { + requireNonNull(student); + personsAdded.add(student); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + tuitionClassesAdded.add(tuitionClass); + } + + @Override + public ReadOnlyTimesTable getTimesTable() { + return new TimesTable(); + } + } + +} diff --git a/src/test/java/seedu/times/logic/commands/ClearCommandTest.java b/src/test/java/seedu/times/logic/commands/ClearCommandTest.java new file mode 100644 index 00000000000..61529206dfe --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/ClearCommandTest.java @@ -0,0 +1,32 @@ +package seedu.times.logic.commands; + +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; + +public class ClearCommandTest { + + @Test + public void execute_emptyTimesTable_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyTimesTable_success() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + expectedModel.setTimesTable(new TimesTable()); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/times/logic/commands/CommandResultTest.java similarity index 98% rename from src/test/java/seedu/address/logic/commands/CommandResultTest.java rename to src/test/java/seedu/times/logic/commands/CommandResultTest.java index 4f3eb46e9ef..e818791ef01 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/times/logic/commands/CommandResultTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/seedu/times/logic/commands/CommandTestUtil.java b/src/test/java/seedu/times/logic/commands/CommandTestUtil.java new file mode 100644 index 00000000000..e0f23ed554c --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/CommandTestUtil.java @@ -0,0 +1,208 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.EditCommand.EditStudentDescriptor; +import static seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASSTIMING; +import static seedu.times.logic.parser.CliSyntax.PREFIX_CLASS_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NOK; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_RATE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.logic.parser.ParserUtil.FIND_REGEX_WITH_COMMA_DELIMITER; +import static seedu.times.testutil.Assert.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.TimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.predicates.ClassNameContainsKeywordsPredicate; +import seedu.times.testutil.EditClassDescriptorBuilder; +import seedu.times.testutil.EditPersonDescriptorBuilder; + +/** + * Contains helper methods for testing commands. + */ +public class CommandTestUtil { + + //// SAMPLE STUDENT INFO + public static final String VALID_NAME_AMY = "Amy Bee"; + public static final String VALID_NAME_BOB = "Bob Choo"; + public static final String VALID_PHONE_AMY = "11111111"; + public static final String VALID_PHONE_BOB = "22222222"; + public static final String VALID_EMAIL_AMY = "amy@example.com"; + public static final String VALID_EMAIL_BOB = "bob@example.com"; + public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; + public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; + public static final String VALID_NAME_NOK = "Papa"; + public static final String VALID_PHONE_NOK = "33333333"; + public static final String VALID_EMAIL_NOK = "papa@example.com"; + public static final String VALID_ADDRESS_NOK = "Block 456, Squid Street 69"; + public static final String VALID_TAG_HUSBAND = "husband"; + public static final String VALID_TAG_FRIEND = "friend"; + + public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; + public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; + public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; + public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB; + public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY; + public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; + public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; + public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; + public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; + public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; + public static final String VALID_PREFIX_NOK = " " + PREFIX_NOK; + public static final String NAME_DESC_NOK = " " + PREFIX_NAME + VALID_NAME_NOK; + public static final String PHONE_DESC_NOK = " " + PREFIX_PHONE + VALID_PHONE_NOK; + public static final String EMAIL_DESC_NOK = " " + PREFIX_EMAIL + VALID_EMAIL_NOK; + public static final String ADDRESS_DESC_NOK = " " + PREFIX_ADDRESS + VALID_ADDRESS_NOK; + + public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names + public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones + public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol + public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses + + //// SAMPLE CLASS INFO + public static final String VALID_CLASSNAME_IB_PHYSICS = "IB Physics"; + public static final String VALID_CLASSNAME_IB_MATHS = "IB Maths"; + public static final String VALID_CLASSTIMING_IB_PHYSICS = "tue 13:00-14:00"; + public static final String VALID_CLASSTIMING_IB_MATHS = "mon 10:00-11:00"; + public static final String VALID_RATE_IB_PHYSICS = "70"; + public static final String VALID_RATE_IB_MATHS = "80"; + public static final String VALID_LOCATION_IB_PHYSICS = "Block 312, Adams Street 1"; + public static final String VALID_LOCATION_IB_MATHS = "Block 123, Brown Street 3"; + public static final String[] VALID_STUDENTLIST_IB_PHYSICS = {"Benson Meier", "Alice Pauline"}; + public static final String[] VALID_STUDENTLIST_IB_MATHS = {"Carl Kurz", "Daniel Meier"}; + public static final String[] VALID_EMPTY_STUDENTLIST = { }; + + public static final String CLASSNAME_DESC_PHYSICS = " " + PREFIX_CLASS_NAME + VALID_CLASSNAME_IB_PHYSICS; + public static final String CLASSNAME_DESC_MATHS = " " + PREFIX_CLASS_NAME + VALID_CLASSNAME_IB_MATHS; + public static final String RATE_DESC_PHYSICS = " " + PREFIX_RATE + VALID_RATE_IB_PHYSICS; + public static final String RATE_DESC_MATHS = " " + PREFIX_RATE + VALID_RATE_IB_MATHS; + public static final String CLASSTIMING_DESC_PHYSICS = " " + PREFIX_CLASSTIMING + VALID_CLASSTIMING_IB_PHYSICS; + public static final String CLASSTIMING_DESC_MATHS = " " + PREFIX_CLASSTIMING + VALID_CLASSTIMING_IB_MATHS; + public static final String LOCATION_DESC_PHYSICS = " " + PREFIX_LOCATION + VALID_LOCATION_IB_PHYSICS; + public static final String LOCATION_DESC_MATHS = " " + PREFIX_LOCATION + VALID_LOCATION_IB_MATHS; + public static final String CLASS_NAME_DESC_PHYSICS = " " + PREFIX_CLASS_NAME + VALID_CLASSNAME_IB_PHYSICS; + public static final String CLASS_NAME_DESC_MATHS = " " + PREFIX_CLASS_NAME + VALID_CLASSNAME_IB_MATHS; + + public static final String INVALID_RATE_DESC = " " + PREFIX_RATE + "-32"; // negative rate not allowed for rate + // empty string not allowed for classTiming + public static final String INVALID_CLASSTIMING_DESC = " " + PREFIX_CLASSTIMING; + public static final String INVALID_LOCATION_DESC = " " + PREFIX_LOCATION; // empty string not allowed for locations + // non-alphanumeric not allowed for className + public static final String INVALID_CLASSNAME_DESC = " " + PREFIX_CLASS_NAME + "IB_MATHS!"; + + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + public static final EditStudentDescriptor DESC_AMY; + public static final EditCommand.EditStudentDescriptor DESC_BOB; + public static final EditClassDescriptor DESC_IB_PHYSICS; + public static final EditClassDescriptor DESC_IB_MATHS; + + static { + DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) + .withTags(VALID_TAG_FRIEND).withNokName(VALID_NAME_NOK).withNokPhone(VALID_PHONE_NOK) + .withNokAddress(VALID_ADDRESS_NOK).withNokEmail(VALID_EMAIL_NOK).build(); + + DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).withNokName(VALID_NAME_NOK) + .withNokPhone(VALID_PHONE_NOK).withNokAddress(VALID_ADDRESS_NOK) + .withNokEmail(VALID_EMAIL_NOK).build(); + + DESC_IB_PHYSICS = new EditClassDescriptorBuilder().withClassName(VALID_CLASSNAME_IB_PHYSICS) + .withClassTiming(VALID_CLASSTIMING_IB_PHYSICS).withRate(VALID_RATE_IB_PHYSICS) + .withLocation(VALID_LOCATION_IB_PHYSICS).withStudentList(VALID_STUDENTLIST_IB_PHYSICS).build(); + + DESC_IB_MATHS = new EditClassDescriptorBuilder().withClassName(VALID_CLASSNAME_IB_MATHS) + .withClassTiming(VALID_CLASSTIMING_IB_MATHS).withRate(VALID_RATE_IB_MATHS) + .withLocation(VALID_LOCATION_IB_MATHS).withStudentList(VALID_STUDENTLIST_IB_PHYSICS).build(); + } + + /** + * Executes the given {@code command}, confirms that
+ * - the returned {@link CommandResult} matches {@code expectedCommandResult}
+ * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + /** + * Executes the given {@code command}, confirms that
+ * - a {@code CommandException} is thrown
+ * - the CommandException message matches {@code expectedMessage}
+ * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged + */ + public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { + // we are unable to defensively copy the model for comparison later, so we can + // only do so by copying its components. + TimesTable expectedTimesTable = new TimesTable(actualModel.getTimesTable()); + List expectedFilteredList = new ArrayList<>(actualModel.getFilteredStudentList()); + + assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); + assertEquals(expectedTimesTable, actualModel.getTimesTable()); + assertEquals(expectedFilteredList, actualModel.getFilteredStudentList()); + } + + /** + * Updates {@code model}'s filtered list to show only the Student at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showStudentAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredStudentList().size()); + + Student student = model.getFilteredStudentList().get(targetIndex.getZeroBased()); + final String[] splitName = student.getName().fullName.split("\\s+"); + model.updateFilteredStudentList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + + assertEquals(1, model.getFilteredStudentList().size()); + } + + /** + * Updates {@code model}'s filtered list to show only the classes at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showClassAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredTuitionClassList().size()); + + TuitionClass tuitionClass = model.getFilteredTuitionClassList().get(targetIndex.getZeroBased()); + final String[] splitName = tuitionClass.getClassName().toString().split(FIND_REGEX_WITH_COMMA_DELIMITER); + model.updateFilteredClassList(new ClassNameContainsKeywordsPredicate(Arrays.asList(splitName))); + + assertEquals(1, model.getFilteredTuitionClassList().size()); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/times/logic/commands/DeleteCommandTest.java similarity index 55% rename from src/test/java/seedu/address/logic/commands/DeleteCommandTest.java rename to src/test/java/seedu/times/logic/commands/DeleteCommandTest.java index 45a8c910ba1..426689c6e0b 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/times/logic/commands/DeleteCommandTest.java @@ -1,22 +1,22 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.CommandTestUtil.showStudentAtIndex; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; import org.junit.jupiter.api.Test; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.person.Student; /** * Contains integration tests (interaction with the Model) and unit tests for @@ -24,40 +24,40 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Student studentToDelete = model.getFilteredStudentList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, studentToDelete); - ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); + ModelManager expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + expectedModel.deleteStudent(studentToDelete); assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); } @Test public void execute_invalidIndexUnfilteredList_throwsCommandException() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredStudentList().size() + 1); DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } @Test public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showStudentAtIndex(model, INDEX_FIRST); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Student studentToDelete = model.getFilteredStudentList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, studentToDelete); - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + expectedModel.deleteStudent(studentToDelete); showNoPerson(expectedModel); assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); @@ -65,27 +65,27 @@ public void execute_validIndexFilteredList_success() { @Test public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showStudentAtIndex(model, INDEX_FIRST); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + assertTrue(outOfBoundIndex.getZeroBased() < model.getTimesTable().getStudentList().size()); DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } @Test public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); + DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST); + DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // different types -> returns false @@ -102,8 +102,8 @@ public void equals() { * Updates {@code model}'s filtered list to show no one. */ private void showNoPerson(Model model) { - model.updateFilteredPersonList(p -> false); + model.updateFilteredStudentList(p -> false); - assertTrue(model.getFilteredPersonList().isEmpty()); + assertTrue(model.getFilteredStudentList().isEmpty()); } } diff --git a/src/test/java/seedu/times/logic/commands/EditCommandTest.java b/src/test/java/seedu/times/logic/commands/EditCommandTest.java new file mode 100644 index 00000000000..918e04721f2 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/EditCommandTest.java @@ -0,0 +1,173 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.CommandTestUtil.DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.CommandTestUtil.showStudentAtIndex; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.EditCommand.EditStudentDescriptor; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; +import seedu.times.model.person.Student; +import seedu.times.testutil.EditPersonDescriptorBuilder; +import seedu.times.testutil.PersonBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditCommand. + */ +public class EditCommandTest { + + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + Student editedStudent = new PersonBuilder().build(); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder(editedStudent).build(); + EditCommand editCommand = new EditCommand(INDEX_FIRST, descriptor); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + expectedModel.setStudent(model.getFilteredStudentList().get(0), editedStudent); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() { + Index indexLastPerson = Index.fromOneBased(model.getFilteredStudentList().size()); + Student lastStudent = model.getFilteredStudentList().get(indexLastPerson.getZeroBased()); + + PersonBuilder personInList = new PersonBuilder(lastStudent); + Student editedStudent = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + + EditCommand.EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); + EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + expectedModel.setStudent(lastStudent, editedStudent); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + EditCommand editCommand = new EditCommand(INDEX_FIRST, new EditStudentDescriptor()); + Student editedStudent = model.getFilteredStudentList().get(INDEX_FIRST.getZeroBased()); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showStudentAtIndex(model, INDEX_FIRST); + + Student studentInFilteredList = model.getFilteredStudentList().get(INDEX_FIRST.getZeroBased()); + Student editedStudent = new PersonBuilder(studentInFilteredList).withName(VALID_NAME_BOB).build(); + EditCommand editCommand = new EditCommand(INDEX_FIRST, + new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + expectedModel.setStudent(model.getFilteredStudentList().get(0), editedStudent); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicatePersonUnfilteredList_failure() { + Student firstStudent = model.getFilteredStudentList().get(INDEX_FIRST.getZeroBased()); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder(firstStudent).build(); + EditCommand editCommand = new EditCommand(INDEX_SECOND, descriptor); + + assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_STUDENT); + } + + @Test + public void execute_duplicatePersonFilteredList_failure() { + showStudentAtIndex(model, INDEX_FIRST); + + // edit person in filtered list into a duplicate in address book + Student studentInList = model.getTimesTable().getStudentList().get(INDEX_SECOND.getZeroBased()); + EditCommand editCommand = new EditCommand(INDEX_FIRST, + new EditPersonDescriptorBuilder(studentInList).build()); + + assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_STUDENT); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredStudentList().size() + 1); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); + EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidPersonIndexFilteredList_failure() { + showStudentAtIndex(model, INDEX_FIRST); + Index outOfBoundIndex = INDEX_SECOND; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getTimesTable().getStudentList().size()); + + EditCommand editCommand = new EditCommand(outOfBoundIndex, + new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); + + assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final EditCommand standardCommand = new EditCommand(INDEX_FIRST, DESC_AMY); + + // same values -> returns true + EditStudentDescriptor copyDescriptor = new EditCommand.EditStudentDescriptor(DESC_AMY); + EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND, DESC_AMY))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST, DESC_BOB))); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/times/logic/commands/EditStudentDescriptorTest.java similarity index 59% rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java rename to src/test/java/seedu/times/logic/commands/EditStudentDescriptorTest.java index e0288792e72..cbae67f2333 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/times/logic/commands/EditStudentDescriptorTest.java @@ -1,26 +1,25 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.logic.commands.CommandTestUtil.DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.times.testutil.EditPersonDescriptorBuilder; -public class EditPersonDescriptorTest { +public class EditStudentDescriptorTest { @Test public void equals() { // same values -> returns true - EditPersonDescriptor descriptorWithSameValues = new EditPersonDescriptor(DESC_AMY); + EditCommand.EditStudentDescriptor descriptorWithSameValues = new EditCommand.EditStudentDescriptor(DESC_AMY); assertTrue(DESC_AMY.equals(descriptorWithSameValues)); // same object -> returns true @@ -36,7 +35,8 @@ public void equals() { assertFalse(DESC_AMY.equals(DESC_BOB)); // different name -> returns false - EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); + EditCommand.EditStudentDescriptor editedAmy = + new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different phone -> returns false @@ -47,7 +47,7 @@ public void equals() { editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); - // different address -> returns false + // different address - > returns false editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/times/logic/commands/ExitCommandTest.java similarity index 60% rename from src/test/java/seedu/address/logic/commands/ExitCommandTest.java rename to src/test/java/seedu/times/logic/commands/ExitCommandTest.java index 9533c473875..259d1de8b79 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/times/logic/commands/ExitCommandTest.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; public class ExitCommandTest { private Model model = new ModelManager(); diff --git a/src/test/java/seedu/times/logic/commands/FindNameCommandTest.java b/src/test/java/seedu/times/logic/commands/FindNameCommandTest.java new file mode 100644 index 00000000000..bee54ca531b --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/FindNameCommandTest.java @@ -0,0 +1,95 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindNameCommandTest { + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void equals() { + NameContainsKeywordsPredicate firstPredicate = + new NameContainsKeywordsPredicate(Collections.singletonList("first")); + NameContainsKeywordsPredicate secondPredicate = + new NameContainsKeywordsPredicate(Collections.singletonList("second")); + + FindNameCommand findFirstCommand = new FindNameCommand(firstPredicate); + FindNameCommand findSecondCommand = new FindNameCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindNameCommand findFirstCommandCopy = new FindNameCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different predicate with different list of keywords -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_singleKeywords_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + NameContainsKeywordsPredicate predicate = preparePredicate("Jeremiah"); + FindNameCommand command = new FindNameCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_singleKeywords_singlePersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + NameContainsKeywordsPredicate predicate = preparePredicate("Kurz"); + FindNameCommand command = new FindNameCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_singleKeywords_multiplePersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + NameContainsKeywordsPredicate predicate = preparePredicate("Meier"); + FindNameCommand command = new FindNameCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_multpleKeywords_multiplePersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 4); + NameContainsKeywordsPredicate predicate = preparePredicate("carl elle meier"); + FindNameCommand command = new FindNameCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private NameContainsKeywordsPredicate preparePredicate(String userInput) { + return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/times/logic/commands/FindTagCommandTest.java b/src/test/java/seedu/times/logic/commands/FindTagCommandTest.java new file mode 100644 index 00000000000..14ba6654cf2 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/FindTagCommandTest.java @@ -0,0 +1,90 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.BENSON; +import static seedu.times.testutil.TypicalTimestable.DANIEL; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.person.predicates.TagsContainsKeywordsPredicate; + +public class FindTagCommandTest { + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void equals() { + TagsContainsKeywordsPredicate firstPredicate = + new TagsContainsKeywordsPredicate(Collections.singletonList("Maths")); + TagsContainsKeywordsPredicate secondPredicate = + new TagsContainsKeywordsPredicate(Collections.singletonList("Physics")); + + FindTagCommand findMathsCommand = new FindTagCommand(firstPredicate); + FindTagCommand findPhysicsCommand = new FindTagCommand(secondPredicate); + + // same object -> returns true + assertTrue(findMathsCommand.equals(findMathsCommand)); + + // same values -> returns true + FindTagCommand findMathsCommandCopy = new FindTagCommand(firstPredicate); + assertTrue(findMathsCommand.equals(findMathsCommandCopy)); + + // different types -> returns false + assertFalse(findMathsCommand.equals(1)); + + // null -> returns false + assertFalse(findMathsCommand.equals(null)); + + // different predicate with different list of keywords -> returns false + assertFalse(findMathsCommand.equals(findPhysicsCommand)); + } + + @Test + public void execute_zeroKeywords_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + TagsContainsKeywordsPredicate predicate = preparePredicate(" "); + FindTagCommand command = new FindTagCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredStudentList()); + } + + @Test + public void execute_singleKeyword_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + TagsContainsKeywordsPredicate predicate = preparePredicate("Maths"); + FindTagCommand command = new FindTagCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, DANIEL), model.getFilteredStudentList()); + } + + @Test + public void execute_multipleKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + TagsContainsKeywordsPredicate predicate = preparePredicate("maths physics"); + FindTagCommand command = new FindTagCommand(predicate); + expectedModel.updateFilteredStudentList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON, DANIEL), model.getFilteredStudentList()); + } + + /** + * Parses {@code userInput} into a {@code TagsContainsKeywordsPredicate}. + */ + private TagsContainsKeywordsPredicate preparePredicate(String userInput) { + return new TagsContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/times/logic/commands/HelpCommandTest.java similarity index 61% rename from src/test/java/seedu/address/logic/commands/HelpCommandTest.java rename to src/test/java/seedu/times/logic/commands/HelpCommandTest.java index 4904fc4352e..307463add29 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/times/logic/commands/HelpCommandTest.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; public class HelpCommandTest { private Model model = new ModelManager(); diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/times/logic/commands/ListCommandTest.java similarity index 50% rename from src/test/java/seedu/address/logic/commands/ListCommandTest.java rename to src/test/java/seedu/times/logic/commands/ListCommandTest.java index 435ff1f7275..c3ace84fe6b 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/times/logic/commands/ListCommandTest.java @@ -1,16 +1,16 @@ -package seedu.address.logic.commands; +package seedu.times.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.CommandTestUtil.showStudentAtIndex; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; /** * Contains integration tests (interaction with the Model) and unit tests for ListCommand. @@ -22,8 +22,8 @@ public class ListCommandTest { @BeforeEach public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); } @Test @@ -33,7 +33,7 @@ public void execute_listIsNotFiltered_showsSameList() { @Test public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showStudentAtIndex(model, INDEX_FIRST); assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); } } diff --git a/src/test/java/seedu/times/logic/commands/SortCommandTest.java b/src/test/java/seedu/times/logic/commands/SortCommandTest.java new file mode 100644 index 00000000000..09eac946ace --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/SortCommandTest.java @@ -0,0 +1,121 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTableReverseClasses; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTableReverseStudents; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; + +class SortCommandTest { + + @Test + void execute_nameInDescOrder_sortNameAsc() { + Model model = new ModelManager(getTypicalTimesTableReverseStudents(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_ASC), model, + String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.STUDENT_TAB_SORTED, + SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_ASC), expectedModel); + } + + @Test + void execute_nameInAscOrder_sortNameAsc() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_ASC), model, + String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.STUDENT_TAB_SORTED, + SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_ASC), expectedModel); + } + + @Test + void execute_nameInDescOrder_sortNameDesc() { + Model model = new ModelManager(getTypicalTimesTableReverseStudents(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_DESC), model, + String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.STUDENT_TAB_SORTED, + SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_DESC), expectedModel); + } + + @Test + void execute_nameInAscOrder_sortNameDesc() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalTimesTableReverseStudents(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_DESC), model, + String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.STUDENT_TAB_SORTED, + SortCommand.SORT_BY_NAME, SortCommand.DIRECTION_OF_SORT_DESC), expectedModel); + } + + @Test + void execute_classInAscOrder_sortTimingAsc() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_ASC), + model, String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.CLASSES_TAB_SORTED, + SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_ASC), expectedModel); + } + + @Test + void execute_classInDescOrder_sortTimingAsc() { + Model model = new ModelManager(getTypicalTimesTableReverseClasses(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_ASC), + model, String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.CLASSES_TAB_SORTED, + SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_ASC), expectedModel); + } + + @Test + void execute_classInAscOrder_sortTimingDesc() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalTimesTableReverseClasses(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_DESC), + model, String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.CLASSES_TAB_SORTED, + SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_DESC), expectedModel); + } + + @Test + void execute_classInDescOrder_sortTimingDesc() { + Model model = new ModelManager(getTypicalTimesTableReverseClasses(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + assertCommandSuccess(new SortCommand(SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_DESC), + model, String.format(SortCommand.MESSAGE_SUCCESS, SortCommand.CLASSES_TAB_SORTED, + SortCommand.SORT_BY_CLASS_TIMING, SortCommand.DIRECTION_OF_SORT_DESC), expectedModel); + } + + @Test + void testEquals() { + SortCommand sortNameAsc = new SortCommand("name", "asc"); + SortCommand sortNameDesc = new SortCommand("name", "desc"); + SortCommand sortTimingAsc = new SortCommand("timing", "asc"); + SortCommand sortTimingDesc = new SortCommand("timing", "desc"); + + //same object + assertTrue(sortNameAsc.equals(sortNameAsc)); + assertTrue(sortNameDesc.equals(sortNameDesc)); + assertTrue(sortTimingAsc.equals(sortTimingAsc)); + assertTrue(sortTimingDesc.equals(sortTimingDesc)); + + //same values + assertTrue(new SortCommand("name", "asc").equals(sortNameAsc)); + assertTrue(new SortCommand("name", "desc").equals(sortNameDesc)); + assertTrue(new SortCommand("timing", "asc").equals(sortTimingAsc)); + assertTrue(new SortCommand("timing", "desc").equals(sortTimingDesc)); + + //different types + assertFalse(sortNameAsc.equals(1)); + + //null + assertFalse(sortNameDesc.equals(null)); + + //different values + assertFalse(sortNameAsc.equals(sortNameDesc)); + assertFalse(sortNameDesc.equals(sortTimingAsc)); + assertFalse(sortTimingAsc.equals(sortTimingDesc)); + assertFalse(sortTimingDesc.equals(sortNameAsc)); + } +} diff --git a/src/test/java/seedu/times/logic/commands/ViewCommandTest.java b/src/test/java/seedu/times/logic/commands/ViewCommandTest.java new file mode 100644 index 00000000000..cadd4913098 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/ViewCommandTest.java @@ -0,0 +1,38 @@ +package seedu.times.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.times.ui.TabName; + +class ViewCommandTest { + + @Test + void testEquals() { + ViewCommand viewTimetable = new ViewCommand(TabName.TIMETABLE); + ViewCommand viewStudents = new ViewCommand(TabName.STUDENTS); + ViewCommand viewClasses = new ViewCommand(TabName.CLASSES); + + //same object + assertTrue(viewClasses.equals(viewClasses)); + assertTrue(viewTimetable.equals(viewTimetable)); + assertTrue(viewStudents.equals(viewStudents)); + + //same values + assertTrue(new ViewCommand(TabName.TIMETABLE).equals(viewTimetable)); + assertTrue(new ViewCommand(TabName.STUDENTS).equals(viewStudents)); + assertTrue(new ViewCommand(TabName.CLASSES).equals(viewClasses)); + + //different types + assertFalse(viewClasses.equals(1)); + + //null + assertFalse(viewClasses.equals(null)); + + //different values + assertFalse(viewClasses.equals(viewStudents)); + + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/AddClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/AddClassCommandTest.java new file mode 100644 index 00000000000..cd5046837ab --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/AddClassCommandTest.java @@ -0,0 +1,286 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.core.Messages; +import seedu.times.logic.commands.CommandResult; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.TimesTable; +import seedu.times.model.person.Name; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.testutil.TimesTableBuilder; +import seedu.times.testutil.TuitionClassBuilder; + +public class AddClassCommandTest { + + @Test + public void constructor_nullTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddClassCommand(null)); + } + + @Test + public void execute_tuitionClassAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingTuitionClassAdded modelStub = new ModelStubAcceptingTuitionClassAdded(); + + //create TuitionClass to be added + TuitionClass classToAdd = new TuitionClassBuilder().build(); + CommandResult commandResult = new AddClassCommand(classToAdd).execute(modelStub); + + assertEquals(String.format(AddClassCommand.MESSAGE_SUCCESS, classToAdd), commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(classToAdd), modelStub.tuitionClassesAdded); + } + + @Test + public void execute_duplicateTuitionClass_throwsCommandException() { + TuitionClass validTuitionClass = new TuitionClassBuilder().build(); + AddClassCommand addClassCommand = new AddClassCommand(validTuitionClass); + ModelStub modelStub = new ModelStubWithTuitionClass(validTuitionClass); + + assertThrows(CommandException.class, Messages.MESSAGE_CLASHING_CLASS_TIMING, () + -> addClassCommand.execute(modelStub)); + } + + @Test + public void execute_tuitionClassWithOverlappingClassTiming_throwsCommandException() { + TuitionClass validTuitionClass1 = new TuitionClassBuilder().withClassTiming("SUN 11:30-13:30").build(); + TuitionClass validTuitionClass2 = new TuitionClassBuilder().withClassTiming("SUN 09:30-12:00").build(); + AddClassCommand addClassCommand = new AddClassCommand(validTuitionClass2); + ModelStub modelStub = new ModelStubWithTuitionClass(validTuitionClass1); + + assertThrows(CommandException.class, Messages.MESSAGE_CLASHING_CLASS_TIMING, () + -> addClassCommand.execute(modelStub)); + } + + @Test + public void equals_className() { + TuitionClass classTiming13 = new TuitionClassBuilder().withClassName("Maths").build(); + TuitionClass classTiming12 = new TuitionClassBuilder().withClassName("Timing13").build(); + AddClassCommand addTiming12ClassCommand = new AddClassCommand(classTiming13); + AddClassCommand addTiming13ClassCommand = new AddClassCommand(classTiming12); + + // same object -> returns true + assertTrue(addTiming12ClassCommand.equals(addTiming12ClassCommand)); + + // same values -> returns true + AddClassCommand addTiming12ClassCommandCopy = new AddClassCommand(classTiming13); + assertTrue(addTiming12ClassCommand.equals(addTiming12ClassCommandCopy)); + + // different types -> returns false + assertFalse(addTiming12ClassCommand.equals("null")); + + // null -> returns false + assertFalse(addTiming12ClassCommand.equals(null)); + + // different tuition class -> returns false + assertFalse(addTiming12ClassCommand.equals(addTiming13ClassCommand)); + } + + @Test + public void equals_classTiming() { + TuitionClass classTiming12 = new TuitionClassBuilder().withClassTiming("TUE 12:00-13:30").build(); + TuitionClass classTiming13 = new TuitionClassBuilder().withClassTiming("TUE 13:00-15:00").build(); + AddClassCommand addTiming12ClassCommand = new AddClassCommand(classTiming12); + AddClassCommand addTiming13ClassCommand = new AddClassCommand(classTiming13); + + // same object -> returns true + assertTrue(addTiming12ClassCommand.equals(addTiming12ClassCommand)); + + // same values -> returns true + AddClassCommand addTiming12ClassCommandCopy = new AddClassCommand(classTiming12); + assertTrue(addTiming12ClassCommand.equals(addTiming12ClassCommandCopy)); + + // different types -> returns false + assertFalse(addTiming12ClassCommand.equals("null")); + + // null -> returns false + assertFalse(addTiming12ClassCommand.equals(null)); + + // different tuition class -> returns false + assertFalse(addTiming12ClassCommand.equals(addTiming13ClassCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getTimesTableFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTableFilePath(Path timesTableFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyTimesTable getTimesTable() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTable(ReadOnlyTimesTable newData) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteStudent(Student target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTuitionClass(TuitionClass target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredStudentList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredClassList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClass(TuitionClass target, TuitionClass editedClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClasses(List classes) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudents(List students) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateClassStudentLists(Name newName, Name oldName) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortStudents(Comparator c) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortClasses(Comparator c) { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single tuition class. + */ + private class ModelStubWithTuitionClass extends ModelStub { + private final TimesTable timesTable; + + ModelStubWithTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + this.timesTable = new TimesTableBuilder().withTuitionClass(tuitionClass).build(); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + timesTable.addTuitionClass(tuitionClass); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingTuitionClassAdded extends ModelStub { + final ArrayList tuitionClassesAdded = new ArrayList<>(); + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + requireNonNull(tuitionClass); + tuitionClassesAdded.add(tuitionClass); + } + + @Override + public ReadOnlyTimesTable getTimesTable() { + return new TimesTable(); + } + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/AddToClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/AddToClassCommandTest.java new file mode 100644 index 00000000000..6dd40423bfb --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/AddToClassCommandTest.java @@ -0,0 +1,125 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.classcommands.AddToClassCommand.MESSAGE_ADD_SUCCESS; +import static seedu.times.logic.commands.classcommands.EditClassCommand.MESSAGE_DUPLICATE_STUDENT; +import static seedu.times.testutil.TestUtil.getClassOneBased; +import static seedu.times.testutil.TestUtil.getIndexList; +import static seedu.times.testutil.TestUtil.getStudentOneBased; +import static seedu.times.testutil.TypicalTimestable.BENSON; +import static seedu.times.testutil.TypicalTimestable.JC_CHEMISTRY; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.ClearCommand; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.testutil.TuitionClassBuilder; + + + +public class AddToClassCommandTest { + @Test + public void execute_addSingleStudentToClass_success() { + //create model to used in actual execution + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + //create command to be tested + AddToClassCommand addToClassCommand = new AddToClassCommand(getIndexList(1, 3)); + + //create expected model by manually setting target class to updated class + Model expectedModel = new ModelManager(new TimesTable(getTypicalTimesTable()), new UserPrefs()); + TuitionClass classToAddto = getClassOneBased(expectedModel, 1); + TuitionClass editedClass = new TuitionClassBuilder(classToAddto) + .withStudentList(classToAddto.getStudentList().getNames()).build(); + editedClass.addStudent(getStudentOneBased(model, 3).getName()); + expectedModel.setClass(classToAddto, editedClass); + + //run assertCommand function with corresponding message + String expectedMessage = MESSAGE_ADD_SUCCESS; + assertCommandSuccess(addToClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_addMultipleStudentsToClass_success() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + AddToClassCommand addToClassCommand = new AddToClassCommand(getIndexList(2, 1, 2)); + + Model expectedModel = new ModelManager(new TimesTable(getTypicalTimesTable()), new UserPrefs()); + TuitionClass classToAddto = getClassOneBased(expectedModel, 2); + TuitionClass editedClass = new TuitionClassBuilder(classToAddto) + .withStudentList(classToAddto.getStudentList().getNames()).build(); + editedClass.addAllStudents( + getStudentOneBased(model, 1).getName(), + getStudentOneBased(model, 2).getName() + ); + expectedModel.setClass(classToAddto, editedClass); + + String expectedMessage = MESSAGE_ADD_SUCCESS; + assertCommandSuccess(addToClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_classIndexOutOfRange_failure() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + AddToClassCommand addToClassCommand = new AddToClassCommand(getIndexList(22, 1, 2)); + assertCommandFailure(addToClassCommand, model, MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + @Test + public void execute_indexOutOfRange_failure() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + AddToClassCommand addToClassCommand = new AddToClassCommand(getIndexList(1, 12, 2)); + assertCommandFailure(addToClassCommand, model, MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + @Test + public void execute_addDuplicateStudentsToClass_failure() { + Model model = new ModelManager(new TimesTable(), new UserPrefs()); + + model.addStudent(BENSON); + TuitionClass newClass = new TuitionClassBuilder(JC_CHEMISTRY).withStudentList(BENSON.getName().fullName) + .build(); + model.addTuitionClass(newClass); + + AddToClassCommand addToClassCommand = new AddToClassCommand(getIndexList(1, 1)); + + String expectedMessage = MESSAGE_DUPLICATE_STUDENT + BENSON.getName(); + assertCommandFailure(addToClassCommand, model, expectedMessage); + } + + @Test + public void equals() { + AddToClassCommand standardCommand = new AddToClassCommand(getIndexList(1, 2, 3)); + + // failure + + //null returns false + assertFalse(standardCommand.equals(null)); + + //different value returns false + assertFalse(standardCommand.equals(new AddToClassCommand(getIndexList(1, 4, 3)))); + + //different command returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // success + + //same obj returns true + assertTrue(standardCommand.equals(standardCommand)); + + //same value returns true + assertTrue(standardCommand.equals(new AddToClassCommand(getIndexList(1, 2, 3)))); + } +} + diff --git a/src/test/java/seedu/times/logic/commands/classcommands/DeleteClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/DeleteClassCommandTest.java new file mode 100644 index 00000000000..0b652796bd0 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/DeleteClassCommandTest.java @@ -0,0 +1,67 @@ +package seedu.times.logic.commands.classcommands; + +import static seedu.times.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.classcommands.DeleteClassCommand.MESSAGE_SUCCESS; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.Messages; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.CommandTestUtil; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.tuitionclass.TuitionClass; + +public class DeleteClassCommandTest { + + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + DeleteClassCommand deleteClassCommand = new DeleteClassCommand(INDEX_FIRST); + TuitionClass tuitionClass = model.getFilteredTuitionClassList().get(0); + + ModelManager expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + expectedModel.deleteTuitionClass(tuitionClass); + + + String expectedMessage = String.format(MESSAGE_SUCCESS, tuitionClass); + assertCommandSuccess(deleteClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTuitionClassList().size() + 1); + DeleteClassCommand deleteClassCommand = new DeleteClassCommand(outOfBoundIndex); + assertCommandFailure(deleteClassCommand, model, Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + CommandTestUtil.showClassAtIndex(model, INDEX_SECOND); + + DeleteClassCommand deleteClassCommand = new DeleteClassCommand(INDEX_FIRST); + TuitionClass tuitionClass = model.getFilteredTuitionClassList().get(INDEX_FIRST.getZeroBased()); + + ModelManager expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + expectedModel.deleteTuitionClass(tuitionClass); + expectedModel.updateFilteredClassList(p -> false); + + String expectedMessage = String.format(MESSAGE_SUCCESS, tuitionClass); + assertCommandSuccess(deleteClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_failure() { + CommandTestUtil.showClassAtIndex(model, INDEX_FIRST); + Index outOfBoundIndex = Index.fromOneBased(2); + DeleteClassCommand deleteClassCommand = new DeleteClassCommand(outOfBoundIndex); + assertCommandFailure(deleteClassCommand, model, Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } + +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/EditClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/EditClassCommandTest.java new file mode 100644 index 00000000000..e7b8780c44e --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/EditClassCommandTest.java @@ -0,0 +1,148 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import static seedu.times.logic.commands.classcommands.EditClassCommand.MESSAGE_EDIT_CLASS_SUCCESS; +import static seedu.times.model.tuitionclass.exceptions.DuplicateClassException.DUPLICATE_CLASS_ERROR_MESSAGE; +import static seedu.times.model.tuitionclass.exceptions.OverlappingClassException.OVERLAP_ERROR_MESSAGE; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalTimestable.JC_MATHS; +import static seedu.times.testutil.TypicalTimestable.JC_PHYSICS; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.testutil.EditClassDescriptorBuilder; +import seedu.times.testutil.TuitionClassBuilder; + +public class EditClassCommandTest { + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + //create model to used in actual execution + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = INDEX_FIRST; + + //create command to be tested + //create descriptor + TuitionClass editedClass = new TuitionClassBuilder().build(); + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(editedClass).build(); + //create command + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + //create expected model by manually setting target class to updated class + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + expectedModel.setClass(model.getFilteredTuitionClassList().get(targetIndex.getZeroBased()), editedClass); + + //run assertCommand function with corresponding message + String expectedMessage = String.format(MESSAGE_EDIT_CLASS_SUCCESS, editedClass); + assertCommandSuccess(editClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = INDEX_FIRST; + + TuitionClass editedClass = new TuitionClassBuilder(JC_PHYSICS).withClassName("Discrete Maths").build(); + EditClassDescriptor descriptor = new EditClassDescriptorBuilder().withClassName("Discrete Maths").build(); + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + expectedModel.setClass(model.getFilteredTuitionClassList().get(targetIndex.getZeroBased()), editedClass); + + String expectedMessage = String.format(MESSAGE_EDIT_CLASS_SUCCESS, editedClass); + assertCommandSuccess(editClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_editedClassIsSameAsOriginal_success() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = INDEX_FIRST; + + TuitionClass editedClass = JC_PHYSICS; + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(JC_PHYSICS).build(); + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + Model expectedModel = new ModelManager(new TimesTable(model.getTimesTable()), new UserPrefs()); + + String expectedMessage = String.format(MESSAGE_EDIT_CLASS_SUCCESS, editedClass);; + assertCommandSuccess(editClassCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_editedClassIsDuplicateOfOther_failure() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = INDEX_FIRST; + + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(JC_MATHS).build(); + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + String expectedMessage = DUPLICATE_CLASS_ERROR_MESSAGE; + assertCommandFailure(editClassCommand, model, expectedMessage); + } + + @Test + public void execute_editedClassOverlapClassTiming_failure() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = INDEX_FIRST; + + //overlap with JC_MATHS + TuitionClass editedClass = new TuitionClassBuilder().build(); + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(editedClass).withClassTiming("Wed 14:00-16:00") + .build(); + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + String expectedMessage = OVERLAP_ERROR_MESSAGE; + assertCommandFailure(editClassCommand, model, expectedMessage); + } + + @Test + public void execute_indexOutOfRange_failure() { + Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + Index targetIndex = Index.fromOneBased(999); + + TuitionClass editedClass = new TuitionClassBuilder().build(); + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(editedClass).build(); + EditClassCommand editClassCommand = new EditClassCommand(targetIndex, descriptor); + + String expectedMessage = MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; + assertCommandFailure(editClassCommand, model, expectedMessage); + } + + + @Test + public void equals() { + TuitionClass editedClass = new TuitionClassBuilder().build(); + EditClassDescriptor descriptor = new EditClassDescriptorBuilder(editedClass).build(); + EditClassCommand editClassCommand = new EditClassCommand(INDEX_FIRST, descriptor); + EditClassCommand editClassCommand2 = new EditClassCommand(INDEX_FIRST, descriptor); + + //same obj -> true + assertTrue(editClassCommand.equals(editClassCommand)); + + //same values -> true + assertTrue(editClassCommand.equals(editClassCommand2)); + + //different Index -> false + editClassCommand2 = new EditClassCommand(INDEX_SECOND, descriptor); + assertFalse(editClassCommand.equals(editClassCommand2)); + + //different descriptor -> false + EditClassDescriptor descriptor2 = new EditClassDescriptorBuilder(editedClass).withLocation("Wellington Street") + .build(); + editClassCommand2 = new EditClassCommand(INDEX_FIRST, descriptor2); + assertFalse(editClassCommand.equals(editClassCommand2)); + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/EditClassDescriptorTest.java b/src/test/java/seedu/times/logic/commands/classcommands/EditClassDescriptorTest.java new file mode 100644 index 00000000000..3c9b5eabcf6 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/EditClassDescriptorTest.java @@ -0,0 +1,79 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.CommandTestUtil.DESC_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.DESC_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_STUDENTLIST_IB_MATHS; +import static seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; + +import org.junit.jupiter.api.Test; + +import seedu.times.testutil.EditClassDescriptorBuilder; + +public class EditClassDescriptorTest { + + + @Test + public void equals() { + // shallow copy -> returns true + EditClassDescriptor descriptorWithSameValues = new EditClassDescriptor(DESC_IB_MATHS); + assertTrue(DESC_IB_MATHS.equals(descriptorWithSameValues)); + + //deep copy with null studentNameList -> returns true + EditClassDescriptor editClassDescriptor1 = new EditClassDescriptorBuilder() + .withClassName(VALID_CLASSNAME_IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_PHYSICS) + .withRate(VALID_RATE_IB_PHYSICS).withLocation(VALID_LOCATION_IB_PHYSICS).build(); + + EditClassDescriptor editClassDescriptor2 = new EditClassDescriptorBuilder() + .withClassName(VALID_CLASSNAME_IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_PHYSICS) + .withRate(VALID_RATE_IB_PHYSICS).withLocation(VALID_LOCATION_IB_PHYSICS).build(); + + assertTrue(editClassDescriptor1.equals(editClassDescriptor2)); + + // same object -> returns true + assertTrue(DESC_IB_MATHS.equals(DESC_IB_MATHS)); + + // null -> returns false + assertFalse(DESC_IB_MATHS.equals(null)); + + // different types -> returns false + assertFalse(DESC_IB_MATHS.equals(5)); + + // different values -> returns false + assertFalse(DESC_IB_MATHS.equals(DESC_IB_PHYSICS)); + + // different class name -> returns false + EditClassDescriptor editedIbPhysics = new EditClassDescriptorBuilder(DESC_IB_PHYSICS) + .withClassName(VALID_CLASSNAME_IB_MATHS).build(); + assertFalse(DESC_IB_PHYSICS.equals(editedIbPhysics)); + + // different class timing -> returns false + editedIbPhysics = new EditClassDescriptorBuilder(DESC_IB_PHYSICS) + .withClassTiming(VALID_CLASSTIMING_IB_MATHS).build(); + assertFalse(DESC_IB_PHYSICS.equals(editedIbPhysics)); + + // different rate -> returns false + editedIbPhysics = new EditClassDescriptorBuilder(DESC_IB_PHYSICS) + .withRate(VALID_RATE_IB_MATHS).build(); + assertFalse(DESC_IB_PHYSICS.equals(editedIbPhysics)); + + //different location -> returns false + editedIbPhysics = new EditClassDescriptorBuilder(DESC_IB_PHYSICS) + .withLocation(VALID_LOCATION_IB_MATHS).build(); + assertFalse(DESC_IB_PHYSICS.equals(editedIbPhysics)); + + //different student list -> returns false + editedIbPhysics = new EditClassDescriptorBuilder(DESC_IB_PHYSICS) + .withStudentList(VALID_STUDENTLIST_IB_MATHS).build(); + assertFalse(DESC_IB_PHYSICS.equals(editedIbPhysics)); + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/FindClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/FindClassCommandTest.java new file mode 100644 index 00000000000..c40cd771bf1 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/FindClassCommandTest.java @@ -0,0 +1,144 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_CLASSES_LISTED_OVERVIEW; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.testutil.TypicalTimestable.JC_CHEMISTRY; +import static seedu.times.testutil.TypicalTimestable.JC_CHEMISTRY_DAY; +import static seedu.times.testutil.TypicalTimestable.JC_PHYSICS; +import static seedu.times.testutil.TypicalTimestable.JC_PHYSICS_CLASS_TIMING; +import static seedu.times.testutil.TypicalTimestable.JC_PHYSICS_DAY; +import static seedu.times.testutil.TypicalTimestable.JC_PHYSICS_TIME; +import static seedu.times.testutil.TypicalTimestable.SEC_CHEMISTRY; +import static seedu.times.testutil.TypicalTimestable.SEC_CHEMISTRY_DAY; +import static seedu.times.testutil.TypicalTimestable.SEC_MATHS; +import static seedu.times.testutil.TypicalTimestable.SEC_MATHS_TIME; +import static seedu.times.testutil.TypicalTimestable.SEC_PHYSICS_CLASS_TIMING; +import static seedu.times.testutil.TypicalTimestable.SEC_PHYSICS_DAY; +import static seedu.times.testutil.TypicalTimestable.SEC_PHYSICS_TIME; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.tuitionclass.predicates.ClassTimingContainsKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindClassCommandTest { + // model stub with a class containing JC_PHYISCS, SEC_PHYSICS, JC_MATHS, SEC_MATHS, + // JC_CHEMISTRY, SEC_CHEMISTRY + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void equals() { + ClassTimingContainsKeywordsPredicate firstPredicate = + new ClassTimingContainsKeywordsPredicate(Collections.singletonList("first")); + ClassTimingContainsKeywordsPredicate secondPredicate = + new ClassTimingContainsKeywordsPredicate(Collections.singletonList("second")); + + FindClassCommand findClassFirstCommand = new FindClassCommand(firstPredicate); + FindClassCommand findClassSecondCommand = new FindClassCommand(secondPredicate); + + // same object -> returns true + assertTrue(findClassFirstCommand.equals(findClassFirstCommand)); + + // same values -> returns true + FindClassCommand findClassFirstCommandCopy = new FindClassCommand(firstPredicate); + assertTrue(findClassFirstCommand.equals(findClassFirstCommandCopy)); + + // different types -> returns false + assertFalse(findClassFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findClassFirstCommand.equals(null)); + + // different predicate with different list of keywords -> returns false + assertFalse(findClassFirstCommand.equals(findClassSecondCommand)); + } + + @Test + public void constructor_nullInput_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new FindClassCommand(null)); + } + + @Test + public void execute_findSingleClass_success() { + // command to execute + ClassTimingContainsKeywordsPredicate predicate = preparePredicate(JC_PHYSICS_CLASS_TIMING); + FindClassCommand command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 1); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(JC_PHYSICS), model.getFilteredTuitionClassList()); + } + + @Test + public void execute_findTwoClasses_success() { + // Both JC Physics and Sec maths are at the same time but different day. + ClassTimingContainsKeywordsPredicate predicate = preparePredicate(JC_PHYSICS_TIME + " " + SEC_MATHS_TIME); + FindClassCommand command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 2); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(JC_PHYSICS, SEC_MATHS), model.getFilteredTuitionClassList()); + + // Both JC Chemistry and Sec Chemistry are at the same day but different time. + predicate = preparePredicate(JC_CHEMISTRY_DAY + " " + SEC_CHEMISTRY_DAY); + command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 2); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(JC_CHEMISTRY, SEC_CHEMISTRY), model.getFilteredTuitionClassList()); + } + + @Test + public void execute_findTwoClassDayTimings_noClassFound() { + // Expected behaviour is that when we specify two timings, no class matches this timing + // so it finds no classes, for the same timing, day, and class timing. + + ClassTimingContainsKeywordsPredicate predicate = + preparePredicate(JC_PHYSICS_CLASS_TIMING + " " + SEC_PHYSICS_CLASS_TIMING); + FindClassCommand command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 0); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + + predicate = + preparePredicate(JC_PHYSICS_DAY + " " + SEC_PHYSICS_DAY); + command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 0); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + + predicate = + preparePredicate(JC_PHYSICS_TIME + " " + SEC_PHYSICS_TIME); + command = new FindClassCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + + expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 0); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private ClassTimingContainsKeywordsPredicate preparePredicate(String userInput) { + return new ClassTimingContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/FindClassNameCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/FindClassNameCommandTest.java new file mode 100644 index 00000000000..82e9b1b6cd9 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/FindClassNameCommandTest.java @@ -0,0 +1,82 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.commons.core.Messages.MESSAGE_CLASSES_LISTED_OVERVIEW; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.parser.ParserUtil.FIND_REGEX_WITH_COMMA_DELIMITER; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; +import seedu.times.model.tuitionclass.predicates.ClassNameContainsKeywordsPredicate; + +public class FindClassNameCommandTest { + private Model model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + + @Test + public void execute_singleKeyword_noClassFound() { + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 0); + ClassNameContainsKeywordsPredicate predicate = preparePredicate("Geography"); + FindClassNameCommand command = new FindClassNameCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_singleKeyword_multipleClassFound() { + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 2); + ClassNameContainsKeywordsPredicate predicate = preparePredicate("Physics"); + FindClassNameCommand command = new FindClassNameCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_multipleTerms_multipleClassFound() { + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 6); + ClassNameContainsKeywordsPredicate predicate = preparePredicate("sec, jc"); + FindClassNameCommand command = new FindClassNameCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_multipleKeywords_singleClassFound() { + String expectedMessage = String.format(MESSAGE_CLASSES_LISTED_OVERVIEW, 1); + ClassNameContainsKeywordsPredicate predicate = preparePredicate("jc physics"); + FindClassNameCommand command = new FindClassNameCommand(predicate); + expectedModel.updateFilteredClassList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + + //same obj + FindClassNameCommand command = + new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(Arrays.asList("Physics", "Maths"))); + assertTrue(command.equals(command)); + + //same values + FindClassNameCommand command2 = + new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(Arrays.asList("Physics", "Maths"))); + assertTrue(command.equals(command2)); + + //different values + command2 = + new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(Arrays.asList("Chemistry", "Maths"))); + assertFalse(command.equals(command2)); + } + + private ClassNameContainsKeywordsPredicate preparePredicate(String userInput) { + return new ClassNameContainsKeywordsPredicate(Arrays.asList(userInput.trim() + .split(FIND_REGEX_WITH_COMMA_DELIMITER))); + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/ListClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/ListClassCommandTest.java new file mode 100644 index 00000000000..9495d34a9e6 --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/ListClassCommandTest.java @@ -0,0 +1,35 @@ +package seedu.times.logic.commands.classcommands; + +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.CommandTestUtil.showClassAtIndex; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.times.model.Model; +import seedu.times.model.ModelManager; +import seedu.times.model.UserPrefs; + +class ListClassCommandTest { + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalTimesTable(), new UserPrefs()); + expectedModel = new ModelManager(model.getTimesTable(), new UserPrefs()); + } + + @Test + public void execute_classListIsNotFiltered_showsSameList() { + assertCommandSuccess(new ListClassCommand(), model, ListClassCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_classListIsFiltered_showsEverything() { + showClassAtIndex(model, INDEX_FIRST); + assertCommandSuccess(new ListClassCommand(), model, ListClassCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommandTest.java new file mode 100644 index 00000000000..7da7e99d59e --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/RemoveFromClassCommandTest.java @@ -0,0 +1,327 @@ +package seedu.times.logic.commands.classcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.times.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.times.logic.commands.classcommands.RemoveFromClassCommand.MESSAGE_REMOVE_SUCCESS; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TestUtil.getIndexList; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.CARL; +import static seedu.times.testutil.TypicalTimestable.getTypicalStudents; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.core.Messages; +import seedu.times.logic.commands.exceptions.CommandException; +import seedu.times.model.Model; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.ReadOnlyUserPrefs; +import seedu.times.model.person.Name; +import seedu.times.model.person.Person; +import seedu.times.model.person.Student; +import seedu.times.model.person.UniqueStudentList; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.model.tuitionclass.UniqueClassList; + +public class RemoveFromClassCommandTest { + + + @Test + public void constructor_nullInput_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new RemoveFromClassCommand(null)); + } + + @Test + public void execute_removeSingleStudent_success() { + // model stub with a class containing ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE. + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(getTypicalStudents()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(1, 1)); + + // expected model + List studentList = getTypicalStudents(); + studentList.remove(ALICE); + ModelStubWithClass expectedModelStub = new ModelStubWithClass(studentList); + + String expectedMessage = MESSAGE_REMOVE_SUCCESS; + assertCommandSuccess(command, modelStubWithClass, expectedMessage, expectedModelStub); + } + + @Test + public void execute_removeMultipleStudents_success() { + // model stub with a class containing ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE. + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(getTypicalStudents()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(1, 1, 3)); + + // expected model + List studentList = getTypicalStudents(); + studentList.remove(ALICE); + studentList.remove(CARL); + ModelStubWithClass expectedModelStub = new ModelStubWithClass(studentList); + + String expectedMessage = MESSAGE_REMOVE_SUCCESS; + assertCommandSuccess(command, modelStubWithClass, expectedMessage, expectedModelStub); + } + + @Test + public void execute_duplicateStudentIndexes_success() { + // model stub with a class containing ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE. + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(getTypicalStudents()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(1, 1, 3, 1)); + + // expected model + List studentList = getTypicalStudents(); + studentList.remove(ALICE); + studentList.remove(CARL); + ModelStubWithClass expectedModelStub = new ModelStubWithClass(studentList); + + String expectedMessage = MESSAGE_REMOVE_SUCCESS; + assertCommandSuccess(command, modelStubWithClass, expectedMessage, expectedModelStub); + + } + + @Test + public void execute_studentIndexGreaterThanNumberOfStudents_throwsCommandException() { + // model stub with a class containing ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE. + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(getTypicalStudents()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(1, 8)); + + String expectedMessage = Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; + assertThrows(CommandException.class, expectedMessage, () -> command.execute(modelStubWithClass)); + } + + + @Test + public void execute_classIndexGreaterThanClassNumber_throwsCommandException() { + // model stub with a class containing ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE. + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(getTypicalStudents()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(2, 1)); + + String expectedMessage = Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; + assertThrows(CommandException.class, expectedMessage, () -> command.execute(modelStubWithClass)); + } + + @Test + public void execute_removeSingleStudentFromEmptyClass_failure() { + // model stub with a empty class; + ModelStubWithClass modelStubWithClass = new ModelStubWithClass(new ArrayList()); + + // command to execute + RemoveFromClassCommand command = new RemoveFromClassCommand(getIndexList(1, 1)); + + String expectedMessage = Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX; + assertThrows(CommandException.class, expectedMessage, () -> command.execute(modelStubWithClass)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getTimesTableFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTableFilePath(Path timesTableFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTimesTable(ReadOnlyTimesTable newData) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyTimesTable getTimesTable() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasStudent(Student student) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteStudent(Student target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTuitionClass(TuitionClass tuitionClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTuitionClass(TuitionClass target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredStudentList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredClassList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClass(TuitionClass target, TuitionClass editedClass) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setClasses(List classes) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setStudents(List students) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateClassStudentLists(Name newName, Name oldName) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortStudents(Comparator c) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortClasses(Comparator c) { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single tuition class. + */ + private class ModelStubWithClass extends ModelStub { + private TuitionClass tuitionClass; + private List studentList; + + ModelStubWithClass(List studentList) { + requireNonNull(studentList); + this.studentList = studentList; + Name[] names = studentList.stream().map(Person::getName) + .collect(Collectors.toList()).toArray(new Name[]{}); + StudentNameList nameList = new StudentNameList(names); + + this.tuitionClass = new TuitionClass(new ClassName("class"), new ClassTiming("MON 13:30-14:30"), + new Location("Home"), new Rate("60"), nameList); + } + + @Override + public ObservableList getFilteredTuitionClassList() { + UniqueClassList classList = new UniqueClassList(); + classList.add(this.tuitionClass); + + return new FilteredList<>(classList.asUnmodifiableObservableList()); + } + + @Override + public ObservableList getFilteredStudentList() { + UniqueStudentList studentList = new UniqueStudentList(); + for (Student student : this.studentList) { + studentList.add(student); + } + return new FilteredList<>(studentList.asUnmodifiableObservableList()); + } + + @Override + public void setClass(TuitionClass classToRemoveFrom, TuitionClass editedClass) { + this.tuitionClass = editedClass; + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof ModelStubWithClass)) { + return false; + } + + // state check + ModelStubWithClass other = (ModelStubWithClass) obj; + return this.tuitionClass.equals(other.tuitionClass); + } + } +} diff --git a/src/test/java/seedu/times/logic/commands/classcommands/SelectClassCommandTest.java b/src/test/java/seedu/times/logic/commands/classcommands/SelectClassCommandTest.java new file mode 100644 index 00000000000..f5fee317b4c --- /dev/null +++ b/src/test/java/seedu/times/logic/commands/classcommands/SelectClassCommandTest.java @@ -0,0 +1,37 @@ +package seedu.times.logic.commands.classcommands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; + +/** + * Note that the changes done by the SelectClassCommand is not reflected in the model, so the changes + * cannot be tested. + */ +public class SelectClassCommandTest { + + @Test + void testEquals() { + SelectClassCommand selectClassCommand = new SelectClassCommand(Index.fromOneBased(1)); + + //same object + assertTrue(selectClassCommand.equals(selectClassCommand)); + + //same values + assertTrue(new SelectClassCommand(Index.fromOneBased(1)).equals(selectClassCommand)); + + //different values + assertFalse(selectClassCommand.equals(new SelectClassCommand(Index.fromOneBased(2)))); + + //different types + assertFalse(selectClassCommand.equals(1)); + + //null + assertFalse(selectClassCommand.equals(null)); + + } + +} diff --git a/src/test/java/seedu/times/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/times/logic/parser/AddCommandParserTest.java new file mode 100644 index 00000000000..708eb2446b6 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/AddCommandParserTest.java @@ -0,0 +1,198 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_NOK; +import static seedu.times.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.times.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.times.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.times.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PREFIX_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.model.tag.Tag.MAX_TAG_LENGTH; +import static seedu.times.model.tag.Tag.MAX_TAG_NUMBER; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_LONG; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_MANY; +import static seedu.times.testutil.TypicalTimestable.AMY; +import static seedu.times.testutil.TypicalTimestable.BOB; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.AddCommand; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.testutil.PersonBuilder; + +public class AddCommandParserTest { + private AddCommandParser parser = new AddCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Student expectedStudent = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + + // multiple tags - all accepted + Student expectedStudentMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) + .build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudentMultipleTags)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Student expectedStudent = new PersonBuilder(AMY).withTags().build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + new AddCommand(expectedStudent)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + + ADDRESS_DESC_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + VALID_ADDRESS_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, expectedMessage); + + // missing nok prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + VALID_ADDRESS_BOB, expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + + VALID_ADDRESS_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, Name.MESSAGE_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, Phone.MESSAGE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, Email.MESSAGE_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, Address.MESSAGE_CONSTRAINTS); + + // invalid nok + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + VALID_PREFIX_NOK, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + + INVALID_ADDRESS_DESC + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK, Name.MESSAGE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + + ADDRESS_DESC_NOK + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + // Too many tags + String userInput = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB; + for (int i = 0; i < MAX_TAG_NUMBER + 1; i++) { + userInput += TAG_DESC_FRIEND + i; + } + userInput += VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK; + assertParseFailure(parser, userInput, MESSAGE_CONSTRAINTS_TOO_MANY); + + // Tag too many characters + String invalidTag = " " + PREFIX_TAG; + for (int i = 0; i < MAX_TAG_LENGTH + 1; i++) { + invalidTag += "A"; + } + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + invalidTag + + VALID_PREFIX_NOK + NAME_DESC_NOK + PHONE_DESC_NOK + EMAIL_DESC_NOK + ADDRESS_DESC_NOK, + MESSAGE_CONSTRAINTS_TOO_LONG); + + } +} diff --git a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java b/src/test/java/seedu/times/logic/parser/ArgumentTokenizerTest.java similarity index 99% rename from src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java rename to src/test/java/seedu/times/logic/parser/ArgumentTokenizerTest.java index c97308935f5..9594bca566c 100644 --- a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java +++ b/src/test/java/seedu/times/logic/parser/ArgumentTokenizerTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/times/logic/parser/CommandParserTestUtil.java similarity index 89% rename from src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java rename to src/test/java/seedu/times/logic/parser/CommandParserTestUtil.java index e4c33515768..cc8145a3a1a 100644 --- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/times/logic/parser/CommandParserTestUtil.java @@ -1,9 +1,9 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.times.logic.commands.Command; +import seedu.times.logic.parser.exceptions.ParseException; /** * Contains helper methods for testing command parsers. diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/times/logic/parser/DeleteCommandParserTest.java similarity index 67% rename from src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java rename to src/test/java/seedu/times/logic/parser/DeleteCommandParserTest.java index 27eaec84450..ffa3583aa52 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/times/logic/parser/DeleteCommandParserTest.java @@ -1,13 +1,13 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteCommand; +import seedu.times.logic.commands.DeleteCommand; /** * As we are only doing white-box testing, our test cases do not cover path variations @@ -22,7 +22,7 @@ public class DeleteCommandParserTest { @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST)); } @Test diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/times/logic/parser/EditCommandParserTest.java similarity index 59% rename from src/test/java/seedu/address/logic/parser/EditCommandParserTest.java rename to src/test/java/seedu/times/logic/parser/EditCommandParserTest.java index 2ff31522486..9b72e44864f 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/times/logic/parser/EditCommandParserTest.java @@ -1,47 +1,51 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_CLASSTIMING_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.times.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.times.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.times.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.times.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.model.tag.Tag.MAX_TAG_LENGTH; +import static seedu.times.model.tag.Tag.MAX_TAG_NUMBER; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_LONG; +import static seedu.times.model.tag.Tag.MESSAGE_CONSTRAINTS_TOO_MANY; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalIndexes.INDEX_THIRD; import org.junit.jupiter.api.Test; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.EditCommand; +import seedu.times.logic.commands.EditCommand.EditStudentDescriptor; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; +import seedu.times.model.tag.Tag; +import seedu.times.testutil.EditPersonDescriptorBuilder; public class EditCommandParserTest { @@ -85,7 +89,7 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag + // no such thing as invalid tag anymore thus no need to test for invalid tags // invalid phone followed by valid email assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); @@ -101,17 +105,33 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); // multiple invalid values, but only the first invalid value is captured - assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, + assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + INVALID_CLASSTIMING_DESC + + VALID_ADDRESS_AMY + VALID_PHONE_AMY, Name.MESSAGE_CONSTRAINTS); + + // Too many tags + String userInput = "1"; + for (int i = 0; i < MAX_TAG_NUMBER + 1; i++) { + userInput += TAG_DESC_FRIEND + i; + } + assertParseFailure(parser, userInput, MESSAGE_CONSTRAINTS_TOO_MANY); + + // Tag too many characters + String invalidTag = " " + PREFIX_TAG; + for (int i = 0; i < MAX_TAG_LENGTH + 1; i++) { + invalidTag += "A"; + } + assertParseFailure(parser, "1" + invalidTag, MESSAGE_CONSTRAINTS_TOO_LONG); } @Test public void parse_allFieldsSpecified_success() { - Index targetIndex = INDEX_SECOND_PERSON; + Index targetIndex = INDEX_SECOND; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; + + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + + TAG_DESC_FRIEND; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -121,10 +141,10 @@ public void parse_allFieldsSpecified_success() { @Test public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_AMY).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -134,9 +154,10 @@ public void parse_someFieldsSpecified_success() { @Test public void parse_oneFieldSpecified_success() { // name - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); + EditCommand.EditStudentDescriptor descriptor = + new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -167,14 +188,15 @@ public void parse_oneFieldSpecified_success() { @Test public void parse_multipleRepeatedFields_acceptsLast() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY - + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; + + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -183,9 +205,9 @@ public void parse_multipleRepeatedFields_acceptsLast() { @Test public void parse_invalidValueFollowedByValidValue_success() { // no other valid values specified - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -200,10 +222,10 @@ public void parse_invalidValueFollowedByValidValue_success() { @Test public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + TAG_EMPTY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); + EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); diff --git a/src/test/java/seedu/times/logic/parser/FindNameCommandParserTest.java b/src/test/java/seedu/times/logic/parser/FindNameCommandParserTest.java new file mode 100644 index 00000000000..a74faf7b85c --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/FindNameCommandParserTest.java @@ -0,0 +1,38 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.FindNameCommand; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; + +public class FindNameCommandParserTest { + + private FindNameCommandParser parser = new FindNameCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, "", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindNameCommand() { + // no leading and trailing whitespaces + FindNameCommand expectedFindNameCommand = + new FindNameCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); + assertParseSuccess(parser, "Alice, Bob", expectedFindNameCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n Alice, \n \t Bob \t", expectedFindNameCommand); + } + +} diff --git a/src/test/java/seedu/times/logic/parser/FindTagCommandParserTest.java b/src/test/java/seedu/times/logic/parser/FindTagCommandParserTest.java new file mode 100644 index 00000000000..e67653ab275 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/FindTagCommandParserTest.java @@ -0,0 +1,33 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.FindTagCommand; +import seedu.times.model.person.predicates.TagsContainsKeywordsPredicate; + +public class FindTagCommandParserTest { + private FindTagCommandParser parser = new FindTagCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTagCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindTagCommand() { + // no leading and trailing whitespaces + FindTagCommand expectedFindTagCommand = + new FindTagCommand(new TagsContainsKeywordsPredicate(Arrays.asList("Maths", "Physics"))); + assertParseSuccess(parser, "Maths, Physics", expectedFindTagCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n Maths, \n \t Physics \t", expectedFindTagCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/times/logic/parser/ParserUtilTest.java similarity index 62% rename from src/test/java/seedu/address/logic/parser/ParserUtilTest.java rename to src/test/java/seedu/times/logic/parser/ParserUtilTest.java index 4256788b1a7..9b0c66faeda 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/times/logic/parser/ParserUtilTest.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.times.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.Collections; @@ -13,23 +13,32 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; +import seedu.times.model.tag.Tag; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; + private static final String INVALID_LOCATION = " "; + private static final String INVALID_CLASSTIMIMG = " "; + private static final String INVALID_RATE = "-32"; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_TAG = ""; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "123456"; private static final String VALID_ADDRESS = "123 Main Street #0505"; + private static final String VALID_LOCATION = "123 Main Street #0505"; + private static final String VALID_CLASSTIMING = "Mon 13:00-14:00"; + private static final String VALID_RATE = "70"; private static final String VALID_EMAIL = "rachel@example.com"; private static final String VALID_TAG_1 = "friend"; private static final String VALID_TAG_2 = "neighbour"; @@ -50,10 +59,10 @@ public void parseIndex_outOfRangeInput_throwsParseException() { @Test public void parseIndex_validInput_success() throws Exception { // No whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex("1")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex("1")); // Leading and trailing whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 ")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex(" 1 ")); } @Test @@ -125,6 +134,75 @@ public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace)); } + @Test + public void parseLocation_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseLocation((String) null)); + } + + @Test + public void parseLocation_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseLocation(INVALID_LOCATION)); + } + + @Test + public void parseLocation_validValueWithoutWhitespace_returnsLocation() throws Exception { + Location expectedLocation = new Location(VALID_LOCATION); + assertEquals(expectedLocation, ParserUtil.parseLocation(VALID_LOCATION)); + } + + @Test + public void parseLocation_validValueWithWhitespace_returnsTrimmedLocation() throws Exception { + String addressWithWhitespace = WHITESPACE + VALID_LOCATION + WHITESPACE; + Location expectedAddress = new Location(VALID_LOCATION); + assertEquals(expectedAddress, ParserUtil.parseLocation(addressWithWhitespace)); + } + + @Test + public void parseClassTiming_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseClassTiming((String) null)); + } + + @Test + public void parseClassTiming_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseClassTiming(INVALID_CLASSTIMIMG)); + } + + @Test + public void parseClassTiming_validValueWithoutWhitespace_returnsClassTiming() throws Exception { + ClassTiming expectedClassTiming = new ClassTiming(VALID_CLASSTIMING); + assertEquals(expectedClassTiming, ParserUtil.parseClassTiming(VALID_CLASSTIMING)); + } + + @Test + public void parseClassTiming_validValueWithWhitespace_returnsTrimmedClassTiming() throws Exception { + String classTimingWithWhitespace = WHITESPACE + VALID_CLASSTIMING + WHITESPACE; + ClassTiming expectedAddress = new ClassTiming(VALID_CLASSTIMING); + assertEquals(expectedAddress, ParserUtil.parseClassTiming(classTimingWithWhitespace)); + } + + @Test + public void parseRate_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseRate((String) null)); + } + + @Test + public void parseRate_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseRate(INVALID_RATE)); + } + + @Test + public void parseRate_validValueWithoutWhitespace_returnsRate() throws Exception { + Rate expectedRate = new Rate(VALID_RATE); + assertEquals(expectedRate, ParserUtil.parseRate(VALID_RATE)); + } + + @Test + public void parseRate_validValueWithWhitespace_returnsTrimmedRate() throws Exception { + String rateWithWhitespace = WHITESPACE + VALID_RATE + WHITESPACE; + Rate expectedRate = new Rate(VALID_RATE); + assertEquals(expectedRate, ParserUtil.parseRate(rateWithWhitespace)); + } + @Test public void parseEmail_null_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null)); diff --git a/src/test/java/seedu/times/logic/parser/SortCommandParserTest.java b/src/test/java/seedu/times/logic/parser/SortCommandParserTest.java new file mode 100644 index 00000000000..11282d9f4e1 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/SortCommandParserTest.java @@ -0,0 +1,86 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.SortCommand; +import seedu.times.logic.parser.exceptions.ParseException; + + + +class SortCommandParserTest { + + private final SortCommandParser parser = new SortCommandParser(); + + @Test + public void parse_emptyArg_throwParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_tooManyArgs_throwParseException() { + assertParseFailure(parser, "name asc desc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "timing desc asc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "name timing desc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "timing name asc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgs_throwParseException() { + assertParseFailure(parser, "name ascending", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_DIRECTIONOFSORT)); + + assertParseFailure(parser, "timing descending", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_DIRECTIONOFSORT)); + + assertParseFailure(parser, "classes desc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_SORTBY)); + + assertParseFailure(parser, "students asc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.INVALID_SORTBY)); + + } + + @Test + public void parse_validArgs_returnsSortCommand() { + assertParseSuccess(parser, "name asc", + new SortCommand("name", "asc")); + + assertParseSuccess(parser, "name desc", + new SortCommand("name", "desc")); + + assertParseSuccess(parser, "timing asc", + new SortCommand("timing", "asc")); + + assertParseSuccess(parser, "timing desc", + new SortCommand("timing", "desc")); + } + + @Test + void checkSortBy_invalidInput_throwsParseException() { + assertThrows(NullPointerException.class, () -> parser.checkSortBy(null)); + assertThrows(ParseException.class, () -> parser.checkSortBy("null")); // invalid keyword + assertThrows(ParseException.class, () -> parser.checkSortBy("names")); // invalid keyword + assertThrows(ParseException.class, () -> parser.checkSortBy("timings")); // invalid keyword + } + + @Test + void checkDirectionOfSort_invalidInput_throwsParseException() { + assertThrows(NullPointerException.class, () -> parser.checkDirectionOfSort(null)); + assertThrows(ParseException.class, () -> parser.checkDirectionOfSort("null")); // invalid keyword + assertThrows(ParseException.class, () -> parser.checkDirectionOfSort("ascending")); // invalid keyword + assertThrows(ParseException.class, () -> parser.checkDirectionOfSort("descending")); // invalid keyword + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/times/logic/parser/TimesTableParserTest.java similarity index 51% rename from src/test/java/seedu/address/logic/parser/AddressBookParserTest.java rename to src/test/java/seedu/times/logic/parser/TimesTableParserTest.java index d9659205b57..11c0e70f9d3 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/times/logic/parser/TimesTableParserTest.java @@ -1,11 +1,11 @@ -package seedu.address.logic.parser; +package seedu.times.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.List; @@ -13,31 +13,30 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; -import seedu.address.testutil.PersonUtil; - -public class AddressBookParserTest { - - private final AddressBookParser parser = new AddressBookParser(); +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.commands.ClearCommand; +import seedu.times.logic.commands.DeleteCommand; +import seedu.times.logic.commands.EditCommand; +import seedu.times.logic.commands.ExitCommand; +import seedu.times.logic.commands.FindNameCommand; +import seedu.times.logic.commands.HelpCommand; +import seedu.times.logic.commands.ListCommand; +import seedu.times.logic.parser.exceptions.ParseException; +import seedu.times.model.person.Student; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.times.testutil.EditPersonDescriptorBuilder; +import seedu.times.testutil.PersonBuilder; +import seedu.times.testutil.PersonUtil; + +public class TimesTableParserTest { + + private final TimesTableParser parser = new TimesTableParser(); @Test public void parseCommand_add() throws Exception { - Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); + Student student = new PersonBuilder().build(); + AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(student)); + assertEquals(new AddCommand(student), command); } @Test @@ -49,17 +48,17 @@ public void parseCommand_clear() throws Exception { @Test public void parseCommand_delete() throws Exception { DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); + assertEquals(new DeleteCommand(INDEX_FIRST), command); } @Test public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); + Student student = new PersonBuilder().build(); + EditCommand.EditStudentDescriptor descriptor = new EditPersonDescriptorBuilder(student).build(); EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + + INDEX_FIRST.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); + assertEquals(new EditCommand(INDEX_FIRST, descriptor), command); } @Test @@ -69,11 +68,11 @@ public void parseCommand_exit() throws Exception { } @Test - public void parseCommand_find() throws Exception { + public void parseCommand_findname() throws Exception { List keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + FindNameCommand command = (FindNameCommand) parser.parseCommand( + FindNameCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(", "))); + assertEquals(new FindNameCommand(new NameContainsKeywordsPredicate(keywords)), command); } @Test diff --git a/src/test/java/seedu/times/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/times/logic/parser/ViewCommandParserTest.java new file mode 100644 index 00000000000..f6490974d72 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/ViewCommandParserTest.java @@ -0,0 +1,60 @@ +package seedu.times.logic.parser; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.ViewCommand; +import seedu.times.ui.TabName; + +class ViewCommandParserTest { + + private final ViewCommandParser parser = new ViewCommandParser(); + + @Test + public void parse_emptyArg_throwParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_tooManyArgs_throwParseException() { + assertParseFailure(parser, "timetable two", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "classes cat", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "students seat", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + + assertParseFailure(parser, "students timetable classes", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgs_throwParseException() { + assertParseFailure(parser, "timetables", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.INVALID_TAB)); + + assertParseFailure(parser, "class", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.INVALID_TAB)); + + assertParseFailure(parser, "student", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.INVALID_TAB)); + } + + @Test + public void parse_validArgs_returnsViewCommand() { + assertParseSuccess(parser, "timetable", + new ViewCommand(TabName.TIMETABLE)); + + assertParseSuccess(parser, "classes", + new ViewCommand(TabName.CLASSES)); + + assertParseSuccess(parser, "students", + new ViewCommand(TabName.STUDENTS)); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/AddClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/AddClassCommandParserTest.java new file mode 100644 index 00000000000..7fc4ff26895 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/AddClassCommandParserTest.java @@ -0,0 +1,107 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.commands.CommandTestUtil.CLASSNAME_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.CLASSNAME_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.CLASSTIMING_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.CLASSTIMING_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_CLASSNAME_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_CLASSTIMING_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_LOCATION_DESC; +import static seedu.times.logic.commands.CommandTestUtil.INVALID_RATE_DESC; +import static seedu.times.logic.commands.CommandTestUtil.LOCATION_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.LOCATION_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.times.logic.commands.CommandTestUtil.RATE_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.RATE_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMPTY_STUDENTLIST; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_PHYSICS; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.classcommands.AddClassCommand; +import seedu.times.logic.parser.classcommandparsers.AddClassCommandParser; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.testutil.TuitionClassBuilder; + +public class AddClassCommandParserTest { + private AddClassCommandParser parser = new AddClassCommandParser(); + + @Test + public void parse_allFieldsPresentAndValid_success() { + TuitionClass expectedTuitionClass = + new TuitionClassBuilder().withClassName(VALID_CLASSNAME_IB_PHYSICS) + .withClassTiming(VALID_CLASSTIMING_IB_PHYSICS).withLocation(VALID_LOCATION_IB_PHYSICS) + .withRate(VALID_RATE_IB_PHYSICS).withStudentList(VALID_EMPTY_STUDENTLIST).build(); + + //whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + + LOCATION_DESC_PHYSICS + RATE_DESC_PHYSICS, new AddClassCommand(expectedTuitionClass)); + + //multiple className - last name accepted + assertParseSuccess(parser, CLASSNAME_DESC_MATHS + CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + + LOCATION_DESC_PHYSICS + RATE_DESC_PHYSICS, new AddClassCommand(expectedTuitionClass)); + + //multiple classTiming - last timing accepted + assertParseSuccess(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_MATHS + CLASSTIMING_DESC_PHYSICS + + LOCATION_DESC_PHYSICS + RATE_DESC_PHYSICS, new AddClassCommand(expectedTuitionClass)); + + //multiple location - last location accepted + assertParseSuccess(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + LOCATION_DESC_MATHS + + LOCATION_DESC_PHYSICS + RATE_DESC_PHYSICS, new AddClassCommand(expectedTuitionClass)); + + //multiple rate - last rate accepted + assertParseSuccess(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + LOCATION_DESC_PHYSICS + + RATE_DESC_MATHS + RATE_DESC_PHYSICS, new AddClassCommand(expectedTuitionClass)); + } + + @Test + public void parse_emptyArgs_failure() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddClassCommand.MESSAGE_USAGE)); //nothing + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddClassCommand.MESSAGE_USAGE)); //whitespace + } + + @Test + public void parse_missingFieldS_failure() { + //missing className + assertParseFailure(parser, CLASSTIMING_DESC_PHYSICS + RATE_DESC_PHYSICS + LOCATION_DESC_PHYSICS, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClassCommand.MESSAGE_USAGE)); + //missing classTiming + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + RATE_DESC_PHYSICS + LOCATION_DESC_PHYSICS, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClassCommand.MESSAGE_USAGE)); + //missing location + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + RATE_DESC_PHYSICS, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClassCommand.MESSAGE_USAGE)); + //missing rate + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + LOCATION_DESC_PHYSICS, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidFields_failure() { + //invalid className + assertParseFailure(parser, INVALID_CLASSNAME_DESC + CLASSTIMING_DESC_PHYSICS + LOCATION_DESC_PHYSICS + + RATE_DESC_PHYSICS, String.format(ClassName.MESSAGE_CONSTRAINTS)); + //invalid classTiming + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + INVALID_CLASSTIMING_DESC + LOCATION_DESC_PHYSICS + + RATE_DESC_PHYSICS, String.format(ClassTiming.MESSAGE_CONSTRAINTS)); + //invalid location + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + INVALID_LOCATION_DESC + + RATE_DESC_PHYSICS, String.format(Location.MESSAGE_CONSTRAINTS)); + //invalid rate + assertParseFailure(parser, CLASSNAME_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + LOCATION_DESC_PHYSICS + + INVALID_RATE_DESC, String.format(Rate.MESSAGE_CONSTRAINTS)); + } + +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/AddToClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/AddToClassCommandParserTest.java new file mode 100644 index 00000000000..449888f5a68 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/AddToClassCommandParserTest.java @@ -0,0 +1,88 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.index.Index.fromOneBased; +import static seedu.times.logic.commands.classcommands.AddToClassCommand.NO_STUDENT_INDEX_PROVIDED_MESSAGE; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.AddToClassCommand; +import seedu.times.logic.parser.classcommandparsers.AddToClassCommandParser; + +public class AddToClassCommandParserTest { + private Index validIndex1 = fromOneBased(1); + private Index validIndex2 = fromOneBased(2); + private Index validIndex3 = fromOneBased(3); + + private AddToClassCommandParser parser = new AddToClassCommandParser(); + + @Test + public void parse_emptyArgs_failure() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); //nothing + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); //whitespace + } + + @Test + public void parse_noStudentIndex_failure() { + assertParseFailure(parser, "1", NO_STUDENT_INDEX_PROVIDED_MESSAGE); //without trailing space + assertParseFailure(parser, "2 ", NO_STUDENT_INDEX_PROVIDED_MESSAGE); //with trailing space + } + + @Test + public void parse_invalidClassIndex_failure() { + //non-digit with no student index + assertParseFailure(parser, "ds", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //non-digit with student index + assertParseFailure(parser, "dsajkdha 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //negative + assertParseFailure(parser, "-3 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //zero + assertParseFailure(parser, "0 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidStudentIndex_failure() { + //non-digit + assertParseFailure(parser, "3 dsa", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //negative + assertParseFailure(parser, "3 -3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //zero + assertParseFailure(parser, "3 0", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + //first valid second invalid + assertParseFailure(parser, "3 2 0", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddToClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_all_success() { + //add single student + List indexArray1 = Arrays.asList(validIndex1, validIndex2); + AddToClassCommand expectedCommand1 = new AddToClassCommand(indexArray1); + assertParseSuccess(parser, "1 2", expectedCommand1); + + //add multiple students + List indexArray2 = Arrays.asList(validIndex1, validIndex1, validIndex2, validIndex3); + AddToClassCommand expectedCommand2 = new AddToClassCommand(indexArray2); + assertParseSuccess(parser, "1 1 2 3", expectedCommand2); + + //duplicate student indices in command will parse sucessfully with only one index in result + List indexArray3 = Arrays.asList(validIndex1, validIndex1, validIndex3); + AddToClassCommand expectedCommand3 = new AddToClassCommand(indexArray3); + assertParseSuccess(parser, "1 1 1 3", expectedCommand3); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/DeleteClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/DeleteClassCommandParserTest.java new file mode 100644 index 00000000000..b07a51bcea4 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/DeleteClassCommandParserTest.java @@ -0,0 +1,47 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.classcommands.DeleteClassCommand; +import seedu.times.logic.parser.classcommandparsers.DeleteClassCommandParser; + +public class DeleteClassCommandParserTest { + private DeleteClassCommandParser parser = new DeleteClassCommandParser(); + + @Test + public void parse_validArgs_success() { + assertParseSuccess(parser, "1", new DeleteClassCommand(INDEX_FIRST)); + } + + @Test + public void parse_invalidArgs_failure() { + //no args + assertParseFailure(parser, "", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + + //empty args + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + + //negative args + assertParseFailure(parser, "-1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + + //zero + assertParseFailure(parser, "0", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + + //float + assertParseFailure(parser, "1.2", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + + //char + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteClassCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/EditClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/EditClassCommandParserTest.java new file mode 100644 index 00000000000..0dc88817d25 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/EditClassCommandParserTest.java @@ -0,0 +1,140 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.commands.CommandTestUtil.CLASSTIMING_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.CLASSTIMING_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.CLASS_NAME_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.CLASS_NAME_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.LOCATION_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.LOCATION_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.RATE_DESC_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.RATE_DESC_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_PHYSICS; +import static seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import static seedu.times.logic.commands.classcommands.EditClassCommand.MESSAGE_NO_FIELD_PROVIDED; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.times.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.times.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.times.testutil.TypicalIndexes.INDEX_THIRD; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.EditClassCommand; +import seedu.times.logic.parser.classcommandparsers.EditClassCommandParser; +import seedu.times.testutil.EditClassDescriptorBuilder; + +public class EditClassCommandParserTest { + + private EditClassCommandParser parser = new EditClassCommandParser(); + + @Test + public void parse_invalidIndex_failure() { + //zero index + assertParseFailure(parser, "0 n/classname", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + + //negative index + assertParseFailure(parser, "-1 n/classname", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + + } + + @Test + public void parse_noMatchingPrefix_failure() { + //invalid prefix + assertParseFailure(parser, "1 bn/", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + + //random string + assertParseFailure(parser, "1 asdoiaudouidskhja", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_missingParts_failure() { + //empty args + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + + //whitespaces + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditClassCommand.MESSAGE_USAGE)); + + //index provided but no field is provided + assertParseFailure(parser, "1", MESSAGE_NO_FIELD_PROVIDED); + + //index provided but no field is provided, with trailing spaces + assertParseFailure(parser, "1 ", MESSAGE_NO_FIELD_PROVIDED); + } + + @Test + public void parse_singleFieldsSpecified_success() { + //class name + Index targetIndex = INDEX_THIRD; + String userInput = targetIndex.getOneBased() + CLASS_NAME_DESC_MATHS; + EditClassDescriptor descriptor = new EditClassDescriptorBuilder().withClassName(VALID_CLASSNAME_IB_MATHS) + .build(); + EditClassCommand expectedCommand = new EditClassCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //class timing + userInput = targetIndex.getOneBased() + CLASSTIMING_DESC_MATHS; + descriptor = new EditClassDescriptorBuilder().withClassTiming(VALID_CLASSTIMING_IB_MATHS) + .build(); + expectedCommand = new EditClassCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //rate + userInput = targetIndex.getOneBased() + RATE_DESC_MATHS; + descriptor = new EditClassDescriptorBuilder().withRate(VALID_RATE_IB_MATHS) + .build(); + expectedCommand = new EditClassCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //location + userInput = targetIndex.getOneBased() + LOCATION_DESC_MATHS; + descriptor = new EditClassDescriptorBuilder().withLocation(VALID_LOCATION_IB_MATHS) + .build(); + expectedCommand = new EditClassCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_allFieldsSpecified_success() { + //note that studentNameList is not included because you can't edit studentNameList with editclass command + + Index targetIndex = INDEX_SECOND; + String userInput = targetIndex.getOneBased() + RATE_DESC_PHYSICS + CLASSTIMING_DESC_PHYSICS + + CLASS_NAME_DESC_PHYSICS + LOCATION_DESC_PHYSICS; + + EditClassDescriptor descriptor = new EditClassDescriptorBuilder() + .withClassName(VALID_CLASSNAME_IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_PHYSICS) + .withRate(VALID_RATE_IB_PHYSICS).withLocation(VALID_LOCATION_IB_PHYSICS).build(); + EditClassCommand expectedCommand = new EditClassCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_FIRST; + String userInput = targetIndex.getOneBased() + RATE_DESC_MATHS + RATE_DESC_PHYSICS + LOCATION_DESC_MATHS + + LOCATION_DESC_PHYSICS + CLASS_NAME_DESC_MATHS + CLASS_NAME_DESC_PHYSICS + CLASSTIMING_DESC_MATHS + + CLASSTIMING_DESC_PHYSICS; + EditClassDescriptor descriptor = new EditClassDescriptorBuilder() + .withClassName(VALID_CLASSNAME_IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_PHYSICS) + .withRate(VALID_RATE_IB_PHYSICS).withLocation(VALID_LOCATION_IB_PHYSICS).build(); + EditClassCommand expectedCommand = new EditClassCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/FindClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/FindClassCommandParserTest.java new file mode 100644 index 00000000000..2f9fa40f296 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/FindClassCommandParserTest.java @@ -0,0 +1,108 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.classcommands.FindClassCommand; +import seedu.times.logic.parser.classcommandparsers.FindClassCommandParser; +import seedu.times.model.tuitionclass.predicates.ClassTimingContainsKeywordsPredicate; + +public class FindClassCommandParserTest { + + private FindClassCommandParser parser = new FindClassCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validDayArgs_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("FRI"))); + assertParseSuccess(parser, "FRI", expectedFindClassCommand); + } + + @Test + public void parse_invalidDayArgs_throwsParseException() { + assertParseFailure(parser, "FRO", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidMultiDayArgs_throwsParseException() { + assertParseFailure(parser, "FRI SAN", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validMultiDayArgs_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("FRI", "SAT"))); + assertParseSuccess(parser, "FRI SAT", expectedFindClassCommand); + } + + @Test + public void parse_validMultiDayArgsWithMultiSpaces_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("FRI", "SAT"))); + assertParseSuccess(parser, "FRI SAT", expectedFindClassCommand); + } + + @Test + public void parse_validTimingArgs_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("11:30-12:30"))); + assertParseSuccess(parser, "11:30-12:30", expectedFindClassCommand); + expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("11:30-23:59"))); + assertParseSuccess(parser, "11:30-23:59", expectedFindClassCommand); + expectedFindClassCommand = + new FindClassCommand(new ClassTimingContainsKeywordsPredicate(Arrays.asList("23:30-23:59"))); + assertParseSuccess(parser, "23:30-23:59", expectedFindClassCommand); + } + + /* Check for boundary values around 23:59 */ + @Test + public void parse_invalidTimingArgs_throwsParseException() { + assertParseFailure(parser, "11:30-12:32", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:30-22:59", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:30-23:58", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:30-24:00", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validMultiTimingArgs_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand( + new ClassTimingContainsKeywordsPredicate(Arrays.asList("11:30-12:30", "12:30-13:30"))); + assertParseSuccess(parser, "11:30-12:30 12:30-13:30", expectedFindClassCommand); + } + + @Test + public void parse_validMultiTimingArgsWithMultiSpaces_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand( + new ClassTimingContainsKeywordsPredicate(Arrays.asList("11:30-12:30", "12:30-13:30"))); + assertParseSuccess(parser, "11:30-12:30 12:30-13:30", expectedFindClassCommand); + } + + @Test + public void parse_validTimingArgsWithTrailingSpaces_returnsFindClassCommand() { + FindClassCommand expectedFindClassCommand = + new FindClassCommand( + new ClassTimingContainsKeywordsPredicate(Arrays.asList("11:30-12:30", "12:30-13:30"))); + assertParseSuccess(parser, " 11:30-12:30 12:30-13:30", expectedFindClassCommand); + } + +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/FindClassNameCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/FindClassNameCommandParserTest.java new file mode 100644 index 00000000000..13a5632cde9 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/FindClassNameCommandParserTest.java @@ -0,0 +1,55 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.commons.core.Messages.MESSAGE_NO_SEARCH_TERMS_ENTERED; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.logic.commands.classcommands.FindClassNameCommand; +import seedu.times.logic.parser.classcommandparsers.FindClassNameCommandParser; +import seedu.times.model.tuitionclass.predicates.ClassNameContainsKeywordsPredicate; + +public class FindClassNameCommandParserTest { + private FindClassNameCommandParser parser = new FindClassNameCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + //empty args + assertParseFailure(parser, "", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassNameCommand.MESSAGE_USAGE)); + + //empty args with spaces + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClassNameCommand.MESSAGE_USAGE)); + + //with only delimiter + assertParseFailure(parser, ",", + String.format(MESSAGE_NO_SEARCH_TERMS_ENTERED)); + + //with delimiter and spaces + assertParseFailure(parser, " , ", + String.format(MESSAGE_NO_SEARCH_TERMS_ENTERED)); + } + + + + @Test + public void parse_validArgs_returnsFindClassNameCommand() { + //single keywords, no leading and trailing whitespaces + FindClassNameCommand expectedCommand = + new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(Arrays.asList("Physics", "Maths"))); + assertParseSuccess(parser, "Physics, Maths", expectedCommand); + + //single keywords, whitespaces between search terms + assertParseSuccess(parser, " Physics, Maths ", expectedCommand); + + //multiple keywords in each search term + expectedCommand = new FindClassNameCommand(new ClassNameContainsKeywordsPredicate(Arrays.asList("JC Physics", + "JC Chemistry"))); + assertParseSuccess(parser, "JC Physics, JC Chemistry", expectedCommand); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/RemoveFromClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/RemoveFromClassCommandParserTest.java new file mode 100644 index 00000000000..6ea2d9b63fc --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/RemoveFromClassCommandParserTest.java @@ -0,0 +1,97 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.commands.classcommands.RemoveFromClassCommand.NO_STUDENT_INDEX_PROVIDED_MESSAGE; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.RemoveFromClassCommand; +import seedu.times.logic.parser.classcommandparsers.RemoveFromClassCommandParser; + +public class RemoveFromClassCommandParserTest { + private RemoveFromClassCommandParser parser = new RemoveFromClassCommandParser(); + + @Test + public void parse_allValidInputs_success() { + Index validIndex = Index.fromOneBased(1); + Index validIndex2 = Index.fromOneBased(2); + Index validIndex3 = Index.fromOneBased(3); + + // remove single student + RemoveFromClassCommand removeFromClassCommand = + new RemoveFromClassCommand(Arrays.asList(validIndex, validIndex)); + assertParseSuccess(parser, "1 1", removeFromClassCommand); + + // remove single student + RemoveFromClassCommand removeFromClassCommand2 = + new RemoveFromClassCommand(Arrays.asList(validIndex, validIndex, validIndex2, validIndex3)); + assertParseSuccess(parser, "1 1 2 3", removeFromClassCommand2); + } + @Test + public void parse_missingArgs_throwsParseException() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1", String.format(NO_STUDENT_INDEX_PROVIDED_MESSAGE)); + } + + @Test + public void parse_negativeStudentIndex_throwsParseException() { + assertParseFailure(parser, "1 -1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 1 -1 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_zeroStudentIndex_throwsParseException() { + assertParseFailure(parser, "1 0", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 1 0 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_zeroClassIndex_throwsParseException() { + assertParseFailure(parser, "0 1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "0 1 3 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_negativeClassIndex_throwsParseException() { + assertParseFailure(parser, "-1 1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "-3 1 3 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + + } + + @Test + public void parse_nonIntegerArguments_throwsParseException() { + //floats + assertParseFailure(parser, "1.1 1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 1.3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + + //other chars + assertParseFailure(parser, "asdf 1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 asdf", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 1 asdf 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1! 2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "2 @#2", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemoveFromClassCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/times/logic/parser/classcommands/SelectClassCommandParserTest.java b/src/test/java/seedu/times/logic/parser/classcommands/SelectClassCommandParserTest.java new file mode 100644 index 00000000000..4b3fdb9fe41 --- /dev/null +++ b/src/test/java/seedu/times/logic/parser/classcommands/SelectClassCommandParserTest.java @@ -0,0 +1,48 @@ +package seedu.times.logic.parser.classcommands; + +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_CLASS_DISPLAYED_INDEX; +import static seedu.times.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.times.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.core.index.Index; +import seedu.times.logic.commands.classcommands.SelectClassCommand; +import seedu.times.logic.parser.classcommandparsers.SelectClassCommandParser; + + +public class SelectClassCommandParserTest { + private SelectClassCommandParser parser = new SelectClassCommandParser(); + + @Test + public void parse_emptyArgs_failure() { + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectClassCommand.MESSAGE_USAGE)); //nothing + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectClassCommand.MESSAGE_USAGE)); //whitespace + } + + @Test + public void parse_validIndex_returnsSelectClassCommand() { + SelectClassCommand expectedSelectClassCommand = + new SelectClassCommand(Index.fromOneBased(1)); + assertParseSuccess(parser, "1", expectedSelectClassCommand); + expectedSelectClassCommand = + new SelectClassCommand(Index.fromOneBased(10)); + assertParseSuccess(parser, "10", expectedSelectClassCommand); + } + + @Test + public void parse_invalidClassIndex_failure() { + //non-digit + assertParseFailure(parser, "ds", MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + //non-digit with student index + assertParseFailure(parser, "oiajsd 1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectClassCommand.MESSAGE_USAGE)); + //negative + assertParseFailure(parser, "-4", MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + //zero + assertParseFailure(parser, "0", MESSAGE_INVALID_CLASS_DISPLAYED_INDEX); + } +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/times/model/ModelManagerTest.java similarity index 56% rename from src/test/java/seedu/address/model/ModelManagerTest.java rename to src/test/java/seedu/times/model/ModelManagerTest.java index 2cf1418d116..d5893f3d365 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/times/model/ModelManagerTest.java @@ -1,12 +1,12 @@ -package seedu.address.model; +package seedu.times.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.times.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.BENSON; import java.nio.file.Path; import java.nio.file.Paths; @@ -14,9 +14,9 @@ import org.junit.jupiter.api.Test; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.testutil.AddressBookBuilder; +import seedu.times.commons.core.GuiSettings; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.times.testutil.TimesTableBuilder; public class ModelManagerTest { @@ -26,7 +26,7 @@ public class ModelManagerTest { public void constructor() { assertEquals(new UserPrefs(), modelManager.getUserPrefs()); assertEquals(new GuiSettings(), modelManager.getGuiSettings()); - assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook())); + assertEquals(new TimesTable(), new TimesTable(modelManager.getTimesTable())); } @Test @@ -37,14 +37,14 @@ public void setUserPrefs_nullUserPrefs_throwsNullPointerException() { @Test public void setUserPrefs_validUserPrefs_copiesUserPrefs() { UserPrefs userPrefs = new UserPrefs(); - userPrefs.setAddressBookFilePath(Paths.get("address/book/file/path")); + userPrefs.setTimesTableFilePath(Paths.get("address/book/file/path")); userPrefs.setGuiSettings(new GuiSettings(1, 2, 3, 4)); modelManager.setUserPrefs(userPrefs); assertEquals(userPrefs, modelManager.getUserPrefs()); // Modifying userPrefs should not modify modelManager's userPrefs UserPrefs oldUserPrefs = new UserPrefs(userPrefs); - userPrefs.setAddressBookFilePath(Paths.get("new/address/book/file/path")); + userPrefs.setTimesTableFilePath(Paths.get("new/address/book/file/path")); assertEquals(oldUserPrefs, modelManager.getUserPrefs()); } @@ -61,47 +61,47 @@ public void setGuiSettings_validGuiSettings_setsGuiSettings() { } @Test - public void setAddressBookFilePath_nullPath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.setAddressBookFilePath(null)); + public void setTimesTableFilePath_nullPath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.setTimesTableFilePath(null)); } @Test - public void setAddressBookFilePath_validPath_setsAddressBookFilePath() { + public void setTimesTableFilePath_validPath_setsTimesTableFilePath() { Path path = Paths.get("address/book/file/path"); - modelManager.setAddressBookFilePath(path); - assertEquals(path, modelManager.getAddressBookFilePath()); + modelManager.setTimesTableFilePath(path); + assertEquals(path, modelManager.getTimesTableFilePath()); } @Test public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.hasPerson(null)); + assertThrows(NullPointerException.class, () -> modelManager.hasStudent(null)); } @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(modelManager.hasPerson(ALICE)); + public void hasPerson_personNotInTimesTable_returnsFalse() { + assertFalse(modelManager.hasStudent(ALICE)); } @Test - public void hasPerson_personInAddressBook_returnsTrue() { - modelManager.addPerson(ALICE); - assertTrue(modelManager.hasPerson(ALICE)); + public void hasPerson_personInTimesTable_returnsTrue() { + modelManager.addStudent(ALICE); + assertTrue(modelManager.hasStudent(ALICE)); } @Test - public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); + public void getFilteredStudentList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredStudentList().remove(0)); } @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); - AddressBook differentAddressBook = new AddressBook(); + TimesTable timesTable = new TimesTableBuilder().withStudent(ALICE).withStudent(BENSON).build(); + TimesTable differentTimesTable = new TimesTable(); UserPrefs userPrefs = new UserPrefs(); // same values -> returns true - modelManager = new ModelManager(addressBook, userPrefs); - ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); + modelManager = new ModelManager(timesTable, userPrefs); + ModelManager modelManagerCopy = new ModelManager(timesTable, userPrefs); assertTrue(modelManager.equals(modelManagerCopy)); // same object -> returns true @@ -113,20 +113,20 @@ public void equals() { // different types -> returns false assertFalse(modelManager.equals(5)); - // different addressBook -> returns false - assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); + // different timesTable -> returns false + assertFalse(modelManager.equals(new ModelManager(differentTimesTable, userPrefs))); // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); - assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + modelManager.updateFilteredStudentList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + assertFalse(modelManager.equals(new ModelManager(timesTable, userPrefs))); // resets modelManager to initial state for upcoming tests - modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + modelManager.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); - differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath")); - assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); + differentUserPrefs.setTimesTableFilePath(Paths.get("differentFilePath")); + assertFalse(modelManager.equals(new ModelManager(timesTable, differentUserPrefs))); } } diff --git a/src/test/java/seedu/times/model/ModelTestUtils.java b/src/test/java/seedu/times/model/ModelTestUtils.java new file mode 100644 index 00000000000..a977b4f1949 --- /dev/null +++ b/src/test/java/seedu/times/model/ModelTestUtils.java @@ -0,0 +1,12 @@ +package seedu.times.model; + +public class ModelTestUtils { + public static final String LONG_STRING_121_CHAR = "dsadasdksjaldjakdasdksjaldjakdasdksjaldjakdasdksjald" + + "jakdasdksjaldsakdjsajkdslakjdsacomakdasdksjaldsakdjsajkdslakjdsadsada"; + public static final String LONG_STRING_120_CHAR = "dsadasdksjaldjakdasdksjaldjakdasdksjaldjakdasdksjald" + + "jakdasdksjaldsakdjsajkdslakjdsacomakdasdksjaldsakdjsajkdslakjdsasada"; + public static final String LONG_STRING_86_CHAR = "cdsadasdksjaldjakdasdksjaldjakdasdksjaldjakd" + + "asdksjaldjakdasdksjaldsakdjsajkdslakjdsddd"; + public static final String LONG_STRING_85_CHAR = "dsadasdksjaldjakdasdksjaldjakdasdksjaldjakdasdksjald" + + "jakdasdksjaldsakdjsajkdslakjsadds"; +} diff --git a/src/test/java/seedu/times/model/TimesTableTest.java b/src/test/java/seedu/times/model/TimesTableTest.java new file mode 100644 index 00000000000..2fdfe248dfa --- /dev/null +++ b/src/test/java/seedu/times/model/TimesTableTest.java @@ -0,0 +1,110 @@ +package seedu.times.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.times.model.person.Student; +import seedu.times.model.person.exceptions.DuplicatePersonException; +import seedu.times.model.tuitionclass.TuitionClass; +import seedu.times.testutil.PersonBuilder; + +public class TimesTableTest { + + private final TimesTable timesTable = new TimesTable(); + + @Test + public void constructor() { + assertEquals(Collections.emptyList(), timesTable.getStudentList()); + } + + @Test + public void resetData_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> timesTable.resetData(null)); + } + + @Test + public void resetData_withValidReadOnlyTimesTable_replacesData() { + TimesTable newData = getTypicalTimesTable(); + timesTable.resetData(newData); + assertEquals(newData, timesTable); + } + + @Test + public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { + // Two persons with the same identity fields + Student editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + List newStudents = Arrays.asList(ALICE, editedAlice); + TimesTableStub newData = new TimesTableStub(newStudents); + + assertThrows(DuplicatePersonException.class, () -> timesTable.resetData(newData)); + } + + @Test + public void hasPerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> timesTable.hasPerson(null)); + } + + @Test + public void hasPerson_personNotInTimesTable_returnsFalse() { + assertFalse(timesTable.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personInTimesTable_returnsTrue() { + timesTable.addStudent(ALICE); + assertTrue(timesTable.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personWithSameIdentityFieldsInTimesTable_returnsTrue() { + timesTable.addStudent(ALICE); + Student editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + assertTrue(timesTable.hasPerson(editedAlice)); + } + + @Test + public void getPersonList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> timesTable.getStudentList().remove(0)); + } + + /** + * A stub ReadOnlyTimesTable whose persons list can violate interface constraints. + */ + private static class TimesTableStub implements ReadOnlyTimesTable { + private final ObservableList students = FXCollections.observableArrayList(); + + TimesTableStub(Collection students) { + this.students.setAll(students); + } + + @Override + public ObservableList getStudentList() { + return students; + } + + @Override + public ObservableList getTuitionClassList() { + return null; + } + + + } + +} diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/times/model/UserPrefsTest.java similarity index 68% rename from src/test/java/seedu/address/model/UserPrefsTest.java rename to src/test/java/seedu/times/model/UserPrefsTest.java index b1307a70d52..a1d1cc2d9a6 100644 --- a/src/test/java/seedu/address/model/UserPrefsTest.java +++ b/src/test/java/seedu/times/model/UserPrefsTest.java @@ -1,6 +1,6 @@ -package seedu.address.model; +package seedu.times.model; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -13,9 +13,9 @@ public void setGuiSettings_nullGuiSettings_throwsNullPointerException() { } @Test - public void setAddressBookFilePath_nullPath_throwsNullPointerException() { + public void setTimesTableFilePath_nullPath_throwsNullPointerException() { UserPrefs userPrefs = new UserPrefs(); - assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null)); + assertThrows(NullPointerException.class, () -> userPrefs.setTimesTableFilePath(null)); } } diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/times/model/person/AddressTest.java similarity index 76% rename from src/test/java/seedu/address/model/person/AddressTest.java rename to src/test/java/seedu/times/model/person/AddressTest.java index dcd3be87b3a..62e655bce58 100644 --- a/src/test/java/seedu/address/model/person/AddressTest.java +++ b/src/test/java/seedu/times/model/person/AddressTest.java @@ -1,8 +1,10 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.model.ModelTestUtils.LONG_STRING_85_CHAR; +import static seedu.times.model.ModelTestUtils.LONG_STRING_86_CHAR; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -27,10 +29,12 @@ public void isValidAddress() { // invalid addresses assertFalse(Address.isValidAddress("")); // empty string assertFalse(Address.isValidAddress(" ")); // spaces only + assertFalse(Address.isValidAddress(LONG_STRING_86_CHAR)); // valid addresses assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355")); assertTrue(Address.isValidAddress("-")); // one character assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address + assertTrue(Address.isValidAddress(LONG_STRING_85_CHAR)); } } diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/times/model/person/EmailTest.java similarity index 87% rename from src/test/java/seedu/address/model/person/EmailTest.java rename to src/test/java/seedu/times/model/person/EmailTest.java index bbcc6c8c98e..88f3c150b2b 100644 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ b/src/test/java/seedu/times/model/person/EmailTest.java @@ -1,12 +1,16 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; public class EmailTest { + public static final String LONG_EMAIL_86_CHAR = "cdsadasdksjaldjakdasdksjaldjakdasdksjalddjakd" + + "asdksjaldjakdasdksjaldsakdjsaj@google.com"; + public static final String LONG_EMAIL_85_CHAR = "cdsadasdksjaldjakdasdksjaldjakdasdksjlddjakd" + + "asdksjaldjakdasdksjaldsakdjsaj@google.com"; @Test public void constructor_null_throwsNullPointerException() { @@ -51,6 +55,7 @@ public void isValidEmail() { assertFalse(Email.isValidEmail("peterjack@-example.com")); // domain name starts with a hyphen assertFalse(Email.isValidEmail("peterjack@example.com-")); // domain name ends with a hyphen assertFalse(Email.isValidEmail("peterjack@example.c")); // top level domain has less than two chars + assertFalse(Email.isValidEmail(LONG_EMAIL_86_CHAR)); // valid email assertTrue(Email.isValidEmail("PeterJack_1190@example.com")); // underscore in local part @@ -64,5 +69,6 @@ public void isValidEmail() { assertTrue(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name assertTrue(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part assertTrue(Email.isValidEmail("e1234567@u.nus.edu")); // more than one period in domain + assertTrue(Email.isValidEmail(LONG_EMAIL_85_CHAR)); } } diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/times/model/person/NameContainsKeywordsPredicateTest.java similarity index 83% rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java rename to src/test/java/seedu/times/model/person/NameContainsKeywordsPredicateTest.java index f136664e017..2755713b6a5 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/times/model/person/NameContainsKeywordsPredicateTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -9,7 +9,8 @@ import org.junit.jupiter.api.Test; -import seedu.address.testutil.PersonBuilder; +import seedu.times.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.times.testutil.PersonBuilder; public class NameContainsKeywordsPredicateTest { @@ -34,7 +35,7 @@ public void equals() { // null -> returns false assertFalse(firstPredicate.equals(null)); - // different person -> returns false + // different predicate which has different keywords -> returns false assertFalse(firstPredicate.equals(secondPredicate)); } @@ -49,8 +50,9 @@ public void test_nameContainsKeywords_returnsTrue() { assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + //todo ZW fix ur tests + //predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); + //assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); // Mixed-case keywords predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); @@ -60,8 +62,9 @@ public void test_nameContainsKeywords_returnsTrue() { @Test public void test_nameDoesNotContainKeywords_returnsFalse() { // Zero keywords + //todo ZW fix ur tests NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); + //assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); // Non-matching keyword predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/times/model/person/NameTest.java similarity index 67% rename from src/test/java/seedu/address/model/person/NameTest.java rename to src/test/java/seedu/times/model/person/NameTest.java index c9801392874..9932ef8e4c7 100644 --- a/src/test/java/seedu/address/model/person/NameTest.java +++ b/src/test/java/seedu/times/model/person/NameTest.java @@ -1,8 +1,10 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.model.ModelTestUtils.LONG_STRING_120_CHAR; +import static seedu.times.model.ModelTestUtils.LONG_STRING_121_CHAR; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -29,6 +31,7 @@ public void isValidName() { assertFalse(Name.isValidName(" ")); // spaces only assertFalse(Name.isValidName("^")); // only non-alphanumeric characters assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters + assertFalse(Name.isValidName(LONG_STRING_121_CHAR)); // valid name assertTrue(Name.isValidName("peter jack")); // alphabets only @@ -36,5 +39,16 @@ public void isValidName() { assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters assertTrue(Name.isValidName("Capital Tan")); // with capital letters assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names + assertTrue(Name.isValidName(LONG_STRING_120_CHAR)); + } + + @Test + public void compareTo() { + Name alice = new Name("Alice"); + Name bernard = new Name("Bernard"); + assertTrue(alice.compareTo(bernard) < 0); + assertTrue(alice.compareTo(alice) == 0); + assertTrue(bernard.compareTo(bernard) == 0); + assertTrue(bernard.compareTo(alice) > 0); } } diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/times/model/person/PhoneTest.java similarity index 84% rename from src/test/java/seedu/address/model/person/PhoneTest.java rename to src/test/java/seedu/times/model/person/PhoneTest.java index 8dd52766a5f..23991dec715 100644 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ b/src/test/java/seedu/times/model/person/PhoneTest.java @@ -1,8 +1,8 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -31,10 +31,14 @@ public void isValidPhone() { assertFalse(Phone.isValidPhone("phone")); // non-numeric assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits + assertFalse(Phone.isValidPhone("93123232233332153432323232")); //> 25 digits + // valid phone numbers assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers assertTrue(Phone.isValidPhone("93121534")); assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers + assertTrue(Phone.isValidPhone("9312323223333215343232323")); // 25 digits + } } diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/times/model/person/StudentTest.java similarity index 64% rename from src/test/java/seedu/address/model/person/PersonTest.java rename to src/test/java/seedu/times/model/person/StudentTest.java index b29c097cfd4..eea156b92a9 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/times/model/person/StudentTest.java @@ -1,26 +1,26 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.BOB; import org.junit.jupiter.api.Test; -import seedu.address.testutil.PersonBuilder; +import seedu.times.testutil.PersonBuilder; -public class PersonTest { +public class StudentTest { @Test public void asObservableList_modifyList_throwsUnsupportedOperationException() { - Person person = new PersonBuilder().build(); - assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0)); + Student student = new PersonBuilder().build(); + assertThrows(UnsupportedOperationException.class, () -> student.getTags().remove(0)); } @Test @@ -32,7 +32,7 @@ public void isSamePerson() { assertFalse(ALICE.isSamePerson(null)); // same name, all other attributes different -> returns true - Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) + Student editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); assertTrue(ALICE.isSamePerson(editedAlice)); @@ -40,9 +40,9 @@ public void isSamePerson() { editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); assertFalse(ALICE.isSamePerson(editedAlice)); - // name differs in case, all other attributes same -> returns false - Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); - assertFalse(BOB.isSamePerson(editedBob)); + // name differs in case, all other attributes same -> returns true + Student editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); + assertTrue(BOB.isSamePerson(editedBob)); // name has trailing spaces, all other attributes same -> returns false String nameWithTrailingSpaces = VALID_NAME_BOB + " "; @@ -53,7 +53,7 @@ public void isSamePerson() { @Test public void equals() { // same values -> returns true - Person aliceCopy = new PersonBuilder(ALICE).build(); + Student aliceCopy = new PersonBuilder(ALICE).build(); assertTrue(ALICE.equals(aliceCopy)); // same object -> returns true @@ -69,7 +69,7 @@ public void equals() { assertFalse(ALICE.equals(BOB)); // different name -> returns false - Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); + Student editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); assertFalse(ALICE.equals(editedAlice)); // different phone -> returns false @@ -88,4 +88,13 @@ public void equals() { editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); assertFalse(ALICE.equals(editedAlice)); } + + @Test + public void isAnyTagsMatching() { + // matches keyword -> returns true + assertTrue(ALICE.isAnyTagsMatching("Maths")); + + // no match -> returns false + assertFalse(ALICE.isAnyTagsMatching("Physics")); + } } diff --git a/src/test/java/seedu/times/model/person/TagsContainsKeywordsPredicateTest.java b/src/test/java/seedu/times/model/person/TagsContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..c28576594aa --- /dev/null +++ b/src/test/java/seedu/times/model/person/TagsContainsKeywordsPredicateTest.java @@ -0,0 +1,61 @@ +package seedu.times.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.person.predicates.TagsContainsKeywordsPredicate; +import seedu.times.testutil.PersonBuilder; + +public class TagsContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("Maths"); + List secondPredicateKeywordList = Arrays.asList("Maths", "Physics"); + + TagsContainsKeywordsPredicate firstPredicate = new TagsContainsKeywordsPredicate(firstPredicateKeywordList); + TagsContainsKeywordsPredicate secondPredicate = new TagsContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + TagsContainsKeywordsPredicate firstPredicateCopy = new TagsContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different predicate which has different keywords -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_tagsMatchesKeywords_returnsTrue() { + // One tag matches with one keyword + TagsContainsKeywordsPredicate predicate = new TagsContainsKeywordsPredicate(Collections.singletonList("Maths")); + assertTrue(predicate.test(new PersonBuilder().withTags("Maths").build())); + + // Multiple tags match with multiple keywords + predicate = new TagsContainsKeywordsPredicate(Arrays.asList("Maths", "Physics")); + assertTrue(predicate.test(new PersonBuilder().withTags("Maths", "Physics").build())); + + // Only one tag matches with one keyword among many keywords + predicate = new TagsContainsKeywordsPredicate(Arrays.asList("Maths", "Physics")); + assertTrue(predicate.test(new PersonBuilder().withTags("Maths").build())); + + // Mixed-case tag matches with Mixed-case keyword + predicate = new TagsContainsKeywordsPredicate(Arrays.asList("pHySics")); + assertTrue(predicate.test(new PersonBuilder().withTags("PhYsICS").build())); + } + +} diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/times/model/person/UniqueStudentListTest.java similarity index 64% rename from src/test/java/seedu/address/model/person/UniquePersonListTest.java rename to src/test/java/seedu/times/model/person/UniqueStudentListTest.java index 1cc5fe9e0fe..587bea5998b 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/times/model/person/UniqueStudentListTest.java @@ -1,13 +1,13 @@ -package seedu.address.model.person; +package seedu.times.model.person; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.BOB; import java.util.Arrays; import java.util.Collections; @@ -15,13 +15,13 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.testutil.PersonBuilder; +import seedu.times.model.person.exceptions.DuplicatePersonException; +import seedu.times.model.person.exceptions.PersonNotFoundException; +import seedu.times.testutil.PersonBuilder; -public class UniquePersonListTest { +public class UniqueStudentListTest { - private final UniquePersonList uniquePersonList = new UniquePersonList(); + private final UniqueStudentList uniquePersonList = new UniqueStudentList(); @Test public void contains_nullPerson_throwsNullPointerException() { @@ -42,7 +42,7 @@ public void contains_personInList_returnsTrue() { @Test public void contains_personWithSameIdentityFieldsInList_returnsTrue() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Student editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) .build(); assertTrue(uniquePersonList.contains(editedAlice)); } @@ -77,29 +77,29 @@ public void setPerson_targetPersonNotInList_throwsPersonNotFoundException() { public void setPerson_editedPersonIsSamePerson_success() { uniquePersonList.add(ALICE); uniquePersonList.setPerson(ALICE, ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(ALICE); - assertEquals(expectedUniquePersonList, uniquePersonList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + expectedUniqueStudentList.add(ALICE); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test public void setPerson_editedPersonHasSameIdentity_success() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Student editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) .build(); uniquePersonList.setPerson(ALICE, editedAlice); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(editedAlice); - assertEquals(expectedUniquePersonList, uniquePersonList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + expectedUniqueStudentList.add(editedAlice); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test public void setPerson_editedPersonHasDifferentIdentity_success() { uniquePersonList.add(ALICE); uniquePersonList.setPerson(ALICE, BOB); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + expectedUniqueStudentList.add(BOB); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test @@ -123,43 +123,43 @@ public void remove_personDoesNotExist_throwsPersonNotFoundException() { public void remove_existingPerson_removesPerson() { uniquePersonList.add(ALICE); uniquePersonList.remove(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - assertEquals(expectedUniquePersonList, uniquePersonList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test public void setPersons_nullUniquePersonList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((UniquePersonList) null)); + assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((UniqueStudentList) null)); } @Test public void setPersons_uniquePersonList_replacesOwnListWithProvidedUniquePersonList() { uniquePersonList.add(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - uniquePersonList.setPersons(expectedUniquePersonList); - assertEquals(expectedUniquePersonList, uniquePersonList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + expectedUniqueStudentList.add(BOB); + uniquePersonList.setPersons(expectedUniqueStudentList); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test public void setPersons_nullList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null)); + assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null)); } @Test public void setPersons_list_replacesOwnListWithProvidedList() { uniquePersonList.add(ALICE); - List personList = Collections.singletonList(BOB); - uniquePersonList.setPersons(personList); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); + List studentList = Collections.singletonList(BOB); + uniquePersonList.setPersons(studentList); + UniqueStudentList expectedUniqueStudentList = new UniqueStudentList(); + expectedUniqueStudentList.add(BOB); + assertEquals(expectedUniqueStudentList, uniquePersonList); } @Test public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() { - List listWithDuplicatePersons = Arrays.asList(ALICE, ALICE); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicatePersons)); + List listWithDuplicateStudents = Arrays.asList(ALICE, ALICE); + assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicateStudents)); } @Test diff --git a/src/test/java/seedu/times/model/tag/TagTest.java b/src/test/java/seedu/times/model/tag/TagTest.java new file mode 100644 index 00000000000..c941bdd45c7 --- /dev/null +++ b/src/test/java/seedu/times/model/tag/TagTest.java @@ -0,0 +1,44 @@ +package seedu.times.model.tag; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class TagTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Tag(null)); + } + + @Test + public void constructor_invalidTagName_throwsIllegalArgumentException() { + String invalidTagName1 = ""; + String invalidTagName2 = " sec 4 e math"; + assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName1)); + assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName2)); + } + + @Test + public void isValidTagName() { + // null tag name + assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); + } + + @Test + public void isNameMatchingIgnoreCase() { + Tag mathsTag = new Tag("Maths"); + //same name, same letter case -> returns true + assertTrue(mathsTag.isNameMatchingIgnoreCase("Maths")); + + //same name, different letter case -> returns true + assertTrue(mathsTag.isNameMatchingIgnoreCase("mAtHS")); + + //different name -> returns false + assertFalse(mathsTag.isNameMatchingIgnoreCase("Physics")); + } + + +} diff --git a/src/test/java/seedu/times/model/tuitionclass/ClassNameTest.java b/src/test/java/seedu/times/model/tuitionclass/ClassNameTest.java new file mode 100644 index 00000000000..46ace434d86 --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/ClassNameTest.java @@ -0,0 +1,46 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.model.ModelTestUtils.LONG_STRING_85_CHAR; +import static seedu.times.model.ModelTestUtils.LONG_STRING_86_CHAR; +import static seedu.times.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ClassNameTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ClassName(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidCLassName = ""; + assertThrows(IllegalArgumentException.class, () -> new ClassName(invalidCLassName)); + } + + @Test + public void isValidClassName() { + //null name + assertThrows(NullPointerException.class, () -> ClassName.isValidClassName(null)); + + //invalid name + assertFalse(ClassName.isValidClassName("")); // empty string + assertFalse(ClassName.isValidClassName(" ")); // spaces only + assertFalse(ClassName.isValidClassName("^")); // only non-alphanumeric characters + assertFalse(ClassName.isValidClassName("physics*")); // contains non-alphanumeric characters + assertFalse(ClassName.isValidClassName("A-Maths")); //contains mix of alphanumeric and non-alphanumeric + assertFalse(ClassName.isValidClassName(LONG_STRING_86_CHAR)); + + //valid name + assertTrue(ClassName.isValidClassName("403")); //numbers only + assertTrue(ClassName.isValidClassName("biology")); //alphabet only + assertTrue(ClassName.isValidClassName("sec 4 physics")); //mix of alphanumeric + assertTrue(ClassName.isValidClassName("CHEMISTRY")); //capital letters + assertTrue(ClassName.isValidClassName("Learning Lab Tuition center Chemistry Sec 4")); //long name + assertTrue(ClassName.isValidClassName(LONG_STRING_85_CHAR)); + } + +} diff --git a/src/test/java/seedu/times/model/tuitionclass/ClassTimingTest.java b/src/test/java/seedu/times/model/tuitionclass/ClassTimingTest.java new file mode 100644 index 00000000000..ca442766953 --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/ClassTimingTest.java @@ -0,0 +1,162 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.testutil.Assert.assertThrows; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +public class ClassTimingTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ClassTiming(null)); + } + + @Test + public void constructor_invalidClassTiming_throwsIllegalArgumentException() { + //Todo + String invalidClassTiming = ""; + assertThrows(IllegalArgumentException.class, () -> new ClassTiming(invalidClassTiming)); + } + + @Test + public void isValidClassTiming() { + // null classTiming + assertThrows(NullPointerException.class, () -> ClassTiming.isValidClassTiming(null)); + + // invalid classTiming + assertFalse(ClassTiming.isValidClassTiming("")); // empty string + assertFalse(ClassTiming.isValidClassTiming(" ")); // spaces only + assertFalse(ClassTiming.isValidClassTiming("mon 9:00-10:00")); // incorrect timing format + assertFalse(ClassTiming.isValidClassTiming("mon 0900-1000")); // incorrect timing format + assertFalse(ClassTiming.isValidClassTiming("mon 10:00")); // missing end time + assertFalse(ClassTiming.isValidClassTiming("09:00-10:00")); // missing day time + assertFalse(ClassTiming.isValidClassTiming("monday 10:00")); // incorrect day format + assertFalse(ClassTiming.isValidClassTiming("THUR 0900-1000")); // incorrect day format + assertFalse(ClassTiming.isValidClassTiming("THU 09:10-10:00")); // does not start on the hour or 30 min mark + assertFalse(ClassTiming.isValidClassTiming("THU 09:00-10:15")); // does not end on the hour or 30 min mark + assertFalse(ClassTiming.isValidClassTiming("THU 09:00-23:58")); // does not end at 23:59 + + + // valid classTiming + assertTrue(ClassTiming.isValidClassTiming("Mon 01:00-02:00")); + assertTrue(ClassTiming.isValidClassTiming("mON 01:00-23:30")); // DAY in weird caps + assertTrue(ClassTiming.isValidClassTiming("mON 01:00-23:59")); // DAY in weird caps + + } + + + @Test + public void formatClassTiming() { + // todo private method + } + + @Test + public void getDayToInt() { + assertEquals(1, new ClassTiming("Mon 01:00-02:00").getDayToInt()); + assertEquals(2, new ClassTiming("Tue 01:00-02:00").getDayToInt()); + assertEquals(3, new ClassTiming("Wed 01:00-02:00").getDayToInt()); + assertEquals(4, new ClassTiming("Thu 01:00-02:00").getDayToInt()); + assertEquals(5, new ClassTiming("Fri 01:00-02:00").getDayToInt()); + assertEquals(6, new ClassTiming("Sat 01:00-02:00").getDayToInt()); + assertEquals(7, new ClassTiming("Sun 01:00-02:00").getDayToInt()); + + } + + @Test + public void isEarlier() { + // same day earlier time + assertTrue(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("wed 15:00-16:00"))); + // earlier day earlier time + assertTrue(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("thu 15:00-16:00"))); + // earlier day later time + assertTrue(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("fri 09:00-10:00"))); + // same day later time + assertFalse(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("wed 09:00-10:00"))); + // later day earlier time + assertFalse(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("mon 15:00-16:00"))); + // later day later time + assertFalse(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("tue 09:00-10:00"))); + // same day same time slot + assertFalse(new ClassTiming("wed 12:00-13:00").isEarlier(new ClassTiming("wed 12:00-13:00"))); + } + + @Test + public void isAfter() { + assertTrue(new ClassTiming("Sat 11:00-12:00") + .isAfter(LocalTime.parse("10:00", DateTimeFormatter.ofPattern("HH:mm")))); + assertFalse(new ClassTiming("Sat 11:00-12:00") + .isAfter(LocalTime.parse("11:00", DateTimeFormatter.ofPattern("HH:mm")))); + assertFalse(new ClassTiming("Sat 11:00-12:00") + .isAfter(LocalTime.parse("12:00", DateTimeFormatter.ofPattern("HH:mm")))); + assertFalse(new ClassTiming("Sat 11:00-12:00") + .isAfter(LocalTime.parse("15:00", DateTimeFormatter.ofPattern("HH:mm")))); + } + + @Test + public void getStartTime() { + assertEquals(LocalTime.parse("11:00", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getStartTime()); + + assertNotEquals(LocalTime.parse("11:01", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getStartTime()); + + assertNotEquals(LocalTime.parse("10:00", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getStartTime()); + + assertEquals(LocalTime.parse("00:00", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 00:00-23:30").getStartTime()); + } + + @Test + public void getEndTime() { + assertEquals(LocalTime.parse("12:00", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getEndTime()); + + assertNotEquals(LocalTime.parse("12:01", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getEndTime()); + + assertNotEquals(LocalTime.parse("15:00", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 11:00-12:00").getEndTime()); + + assertEquals(LocalTime.parse("23:30", DateTimeFormatter.ofPattern("HH:mm")), + new ClassTiming("Sat 00:00-23:30").getEndTime()); + } + + @Test + public void getClassTiming() { + assertEquals("00:00-23:59", new ClassTiming("moN 00:00-23:59").getClassTiming()); + assertEquals("00:00-23:59", new ClassTiming("Tue 00:00-23:59").getClassTiming()); + assertEquals("00:00-23:59", new ClassTiming("wEd 00:00-23:59").getClassTiming()); + assertEquals("00:00-23:30", new ClassTiming("THU 00:00-23:30").getClassTiming()); + assertEquals("00:00-23:30", new ClassTiming("fri 00:00-23:30").getClassTiming()); + assertEquals("00:00-23:30", new ClassTiming("sAT 00:00-23:30").getClassTiming()); + assertEquals("00:00-23:30", new ClassTiming("SuN 00:00-23:30").getClassTiming()); + + assertNotEquals("00:01-23:30", new ClassTiming("MON 00:00-23:59").getClassTiming()); + } + + @Test + public void compareTo() { + // same day earlier time + assertEquals(-1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("wed 15:00-16:00"))); + // earlier day earlier time + assertEquals(-1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("thu 15:00-16:00"))); + // earlier day later time + assertEquals(-1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("fri 09:00-10:00"))); + // same day later time + assertEquals(1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("wed 09:00-10:00"))); + // later day earlier time + assertEquals(1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("mon 15:00-16:00"))); + // later day later time + assertEquals(1, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("tue 09:00-10:00"))); + // same day same time slot + assertEquals(0, new ClassTiming("wed 12:00-13:00").compareTo(new ClassTiming("wed 12:00-13:00"))); + } +} diff --git a/src/test/java/seedu/times/model/tuitionclass/LocationTest.java b/src/test/java/seedu/times/model/tuitionclass/LocationTest.java new file mode 100644 index 00000000000..8e3c6a6348e --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/LocationTest.java @@ -0,0 +1,42 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.model.ModelTestUtils.LONG_STRING_85_CHAR; +import static seedu.times.model.ModelTestUtils.LONG_STRING_86_CHAR; +import static seedu.times.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class LocationTest { + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Location(null)); + } + + @Test + public void constructor_invalidLocation_throwsIllegalArgumentException() { + String invalidLocation = ""; + String invalidLocation2 = " "; + assertThrows(IllegalArgumentException.class, () -> new Location(invalidLocation)); + assertThrows(IllegalArgumentException.class, () -> new Location(invalidLocation2)); + } + + @Test + public void isValidLocation() { + //null location + assertThrows(NullPointerException.class, () -> Location.isValidLocation(null)); + + //invalid locations + assertFalse(Location.isValidLocation("")); + assertFalse(Location.isValidLocation(" ")); + assertFalse(Location.isValidLocation(" Serangoon")); + assertFalse(Location.isValidLocation(LONG_STRING_86_CHAR)); + + //valid locations + assertTrue(Location.isValidLocation("Nex Kumon")); //no non-alpha numeric + assertTrue(Location.isValidLocation("Hougang blk 111 #02-310")); //contains non-alpha numeric + assertTrue(Location.isValidLocation(LONG_STRING_85_CHAR)); + } + +} diff --git a/src/test/java/seedu/times/model/tuitionclass/RateTest.java b/src/test/java/seedu/times/model/tuitionclass/RateTest.java new file mode 100644 index 00000000000..94719b4dfc8 --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/RateTest.java @@ -0,0 +1,53 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class RateTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Rate(null)); + } + + @Test + public void constructor_negativeRate_throwsIllegalArgumentException() { + String negativeRate = "-32"; + assertThrows(IllegalArgumentException.class, () -> new Rate(negativeRate)); + } + + @Test + public void constructor_emptyRate_throwsIllegalArgumentException() { + String emptyRate1 = ""; + String emptyRate2 = " "; + assertThrows(IllegalArgumentException.class, () -> new Rate(emptyRate1)); + assertThrows(IllegalArgumentException.class, () -> new Rate(emptyRate2)); + } + + @Test + public void isValidRate() { + //null rate + assertThrows(NullPointerException.class, () -> Rate.isValidRate(null)); + + //invalid rate + assertFalse(Rate.isValidRate("")); + assertFalse(Rate.isValidRate(" ")); + assertFalse(Rate.isValidRate("-42")); + assertFalse(Rate.isValidRate("32.3232")); + assertFalse(Rate.isValidRate(".32")); + assertFalse(Rate.isValidRate(".3232")); + assertFalse(Rate.isValidRate("1000000")); + assertFalse(Rate.isValidRate("9999000")); + + //valid rate + assertTrue(Rate.isValidRate("50")); + assertTrue(Rate.isValidRate("50.3")); + assertTrue(Rate.isValidRate("40.40")); + assertTrue(Rate.isValidRate("0")); + assertTrue(Rate.isValidRate("999999.99")); + } +} + diff --git a/src/test/java/seedu/times/model/tuitionclass/StudentNameListTest.java b/src/test/java/seedu/times/model/tuitionclass/StudentNameListTest.java new file mode 100644 index 00000000000..45a07acec51 --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/StudentNameListTest.java @@ -0,0 +1,134 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.person.Name; +import seedu.times.model.tuitionclass.exceptions.DuplicateStudentInClassException; + +/** + * Tests for {@code StudentNameList} + * Contains integration test with {@code Name} + */ +public class StudentNameListTest { + private Name amelia = new Name("Amelia"); + private Name jake = new Name("Jake"); + private Name peter = new Name("Peter"); + private Name wang = new Name("Wang Lu Xun"); + private Name naomi = new Name("Naomi"); + private Name terry = new Name("Terry"); + private Name guoJun = new Name("Guo Jun"); + private Name yaoMing = new Name("Yao Ming"); + + private List nameList = new ArrayList<>(Arrays.asList(amelia, jake, peter, wang)); + + private String[] validNames1 = {"Amelia", "Jake", "Peter", "Wang Lu Xun"}; + private String[] validNames2 = {"Guo Jun", "Naomi", "Terry", "Yao Ming"}; + private String[] validNamesCombined = {"Amelia", "Jake", "Peter", "Wang Lu Xun", "Guo Jun", "Naomi", + "Terry", "Yao Ming"}; + private StudentNameList studentNameListSampleOne = new StudentNameList(validNames1); + private StudentNameList studentNameListSampleTwo = new StudentNameList(validNames2); + private StudentNameList studentNameListSampleCombined = new StudentNameList(validNamesCombined); + + @Test void equals() { + String[] validNames3 = {"Jake", "Amelia", "Peter", "Wang Lu Xun"}; + + //same obj returns true + assertTrue(studentNameListSampleOne.equals(studentNameListSampleOne)); + + //list of same name returns true + assertTrue(studentNameListSampleOne.equals(new StudentNameList(validNames1))); + + //different list returns false + assertFalse(studentNameListSampleOne.equals(studentNameListSampleTwo)); + + //same list with different order returns false + assertFalse(studentNameListSampleOne.equals(new StudentNameList(validNames3))); + } + + @Test + public void contains() { + //null + assertThrows(NullPointerException.class, () -> studentNameListSampleOne.contains(null)); + + //contains returns true + assertTrue(studentNameListSampleOne.contains(amelia)); + assertTrue(studentNameListSampleOne.contains(jake)); + assertTrue(studentNameListSampleTwo.contains(guoJun)); + assertTrue(studentNameListSampleTwo.contains(naomi)); + + //not contain returns false + assertFalse(studentNameListSampleOne.contains(yaoMing)); + assertFalse(studentNameListSampleOne.contains(terry)); + assertFalse(studentNameListSampleTwo.contains(wang)); + assertFalse(studentNameListSampleTwo.contains(peter)); + } + + @Test + public void add() { + StudentNameList studentList = new StudentNameList(validNames1); + + //null + assertThrows(NullPointerException.class, () -> studentList.add(null)); + + //duplicate + assertThrows(DuplicateStudentInClassException.class, () -> studentList.add(jake)); + + //non-duplicate adds successfully + studentList.add(guoJun); + studentList.add(naomi); + studentList.add(terry); + studentList.add(yaoMing); + assertEquals(studentList, studentNameListSampleCombined); + } + + @Test + public void addAll() { + //duplicates are not added + StudentNameList studentList = new StudentNameList(validNames1); + studentList.addAll(studentNameListSampleOne); + assertEquals(studentList, studentNameListSampleOne); + + //new names are added successfully + StudentNameList studentList2 = new StudentNameList(validNames2); + studentList.addAll(studentList2); + assertEquals(studentList, studentNameListSampleCombined); + } + + @Test + public void remove() { + StudentNameList studentList = new StudentNameList(validNamesCombined); + + //remove a name that doesn't exists does nothing + studentList.remove(new Name("Maurice")); + assertEquals(studentList, studentNameListSampleCombined); + + //remove names that exist removes them successfully + studentList.remove(amelia); + studentList.remove(jake); + studentList.remove(peter); + studentList.remove(wang); + assertEquals(studentList, studentNameListSampleTwo); + } + + @Test + public void removeAll() { + StudentNameList studentList = new StudentNameList(validNamesCombined); + + //remove names that don't exists does nothing + studentList.removeAll(Arrays.asList(new Name("James"), new Name("Kobe"))); + assertEquals(studentList, studentNameListSampleCombined); + + //remove names that exist removes them successfully + studentList.removeAll(Arrays.asList(peter, wang, jake, amelia)); + assertEquals(studentList, studentNameListSampleTwo); + } +} diff --git a/src/test/java/seedu/times/model/tuitionclass/TuitionClassTest.java b/src/test/java/seedu/times/model/tuitionclass/TuitionClassTest.java new file mode 100644 index 00000000000..760cc94651b --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/TuitionClassTest.java @@ -0,0 +1,126 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_STUDENTLIST_IB_MATHS; +import static seedu.times.testutil.TypicalTimestable.IB_MATHS; +import static seedu.times.testutil.TypicalTimestable.IB_PHYSICS; + +import org.junit.jupiter.api.Test; + +import seedu.times.testutil.TuitionClassBuilder; + +public class TuitionClassTest { + private ClassName className = new ClassName("test"); + private Location location = new Location("test"); + private Rate rate = new Rate("50"); + + @Test + public void isSameClass() { + //same object -> returns true + assertTrue(IB_PHYSICS.isSameClass(IB_PHYSICS)); + + //null -> returns false + assertFalse(IB_PHYSICS.isSameClass(null)); + + //same class timing, all other attribtues different -> returns true + TuitionClass editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withClassName(VALID_CLASSNAME_IB_MATHS) + .withLocation(VALID_LOCATION_IB_MATHS).withRate(VALID_RATE_IB_MATHS) + .withStudentList(VALID_STUDENTLIST_IB_MATHS).build(); + assertTrue(IB_PHYSICS.isSameClass(editedIbPhysics)); + + //different class timing, all other attributes same -> returns false + editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_MATHS).build(); + assertFalse(IB_PHYSICS.isSameClass(editedIbPhysics)); + } + + @Test + public void equals() { + // same values -> returns true + TuitionClass iBPhysicsCopy = new TuitionClassBuilder(IB_PHYSICS).build(); + assertTrue(IB_PHYSICS.equals(iBPhysicsCopy)); + + // same object -> returns true + assertTrue(IB_PHYSICS.equals(IB_PHYSICS)); + + // null -> returns false + assertFalse(IB_PHYSICS.equals(null)); + + // different type -> returns false + assertFalse(IB_PHYSICS.equals(5)); + + // different classes -> returns false + assertFalse(IB_PHYSICS.equals(IB_MATHS)); + + // different class name -> returns false + TuitionClass editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withClassName(VALID_CLASSNAME_IB_MATHS) + .build(); + assertFalse(IB_PHYSICS.equals(editedIbPhysics)); + + // different class timing -> returns false + editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withClassTiming(VALID_CLASSTIMING_IB_MATHS) + .build(); + assertFalse(IB_PHYSICS.equals(editedIbPhysics)); + + // different rate -> returns false + editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withRate(VALID_RATE_IB_MATHS) + .build(); + assertFalse(IB_PHYSICS.equals(editedIbPhysics)); + + // different location -> returns false + editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withLocation(VALID_LOCATION_IB_MATHS) + .build(); + assertFalse(IB_PHYSICS.equals(editedIbPhysics)); + + // different student list -> returns false + editedIbPhysics = new TuitionClassBuilder(IB_PHYSICS).withStudentList(VALID_STUDENTLIST_IB_MATHS) + .build(); + assertFalse(IB_PHYSICS.equals(editedIbPhysics)); + } + + + //// isOverlapping + @Test + public void isOverlapping_overlappingTiming_returnsTrue() { + TuitionClass class1 = new TuitionClass(className, new ClassTiming("MON 16:00-18:00"), location, rate); + TuitionClass class2 = new TuitionClass(className, new ClassTiming("MON 17:00-18:00"), location, rate); + assertTrue(class1.isOverlapping(class2)); + assertTrue(class2.isOverlapping(class1)); + } + + @Test + public void isOverlapping_oneClassWithinOther_returnsTrue() { + TuitionClass class1 = new TuitionClass(className, new ClassTiming("MON 16:00-18:00"), location, rate); + TuitionClass class2 = new TuitionClass(className, new ClassTiming("MON 15:00-19:00"), location, rate); + assertTrue(class1.isOverlapping(class2)); + assertTrue(class2.isOverlapping(class1)); + } + + @Test + public void isOverlapping_differentDay_returnsFalse() { + TuitionClass class1 = new TuitionClass(className, new ClassTiming("MON 16:00-18:00"), location, rate); + TuitionClass class2 = new TuitionClass(className, new ClassTiming("TUE 17:00-18:00"), location, rate); + assertFalse(class1.isOverlapping(class2)); + assertFalse(class2.isOverlapping(class1)); + } + + @Test + public void isOverlapping_sameDayNoOverlap_returnsFalse() { + TuitionClass class1 = new TuitionClass(className, new ClassTiming("MON 15:00-16:00"), location, rate); + TuitionClass class2 = new TuitionClass(className, new ClassTiming("MON 17:00-18:00"), location, rate); + assertFalse(class1.isOverlapping(class2)); + assertFalse(class2.isOverlapping(class1)); + } + + @Test + public void isOverlapping_sameDayBackToBack_returnsFalse() { + TuitionClass class1 = new TuitionClass(className, new ClassTiming("MON 15:00-17:00"), location, rate); + TuitionClass class2 = new TuitionClass(className, new ClassTiming("MON 17:00-18:00"), location, rate); + assertFalse(class1.isOverlapping(class2)); + assertFalse(class2.isOverlapping(class1)); + } +} diff --git a/src/test/java/seedu/times/model/tuitionclass/UniqueClassListTest.java b/src/test/java/seedu/times/model/tuitionclass/UniqueClassListTest.java new file mode 100644 index 00000000000..3f8505fb4e4 --- /dev/null +++ b/src/test/java/seedu/times/model/tuitionclass/UniqueClassListTest.java @@ -0,0 +1,170 @@ +package seedu.times.model.tuitionclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.JC_CHEMISTRY; +import static seedu.times.testutil.TypicalTimestable.JC_MATHS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.times.model.tuitionclass.exceptions.DuplicateClassException; +import seedu.times.model.tuitionclass.exceptions.OverlappingClassException; +import seedu.times.model.tuitionclass.exceptions.TuitionClassNotFoundException; +import seedu.times.testutil.TuitionClassBuilder; + +public class UniqueClassListTest { + private final UniqueClassList uniqueClassList = new UniqueClassList(); + + @Test + public void contains_nullTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.contains(null)); + } + + @Test + public void contains_tuitionClassNotInList_returnsFalse() { + assertFalse(uniqueClassList.contains(JC_CHEMISTRY)); + } + + @Test + public void contains_tuitionClassInList_returnsTrue() { + uniqueClassList.add(JC_CHEMISTRY); + assertTrue(uniqueClassList.contains(JC_CHEMISTRY)); + } + + @Test + public void contains_tuitionClassWithSameIdentityFieldsInList_returnsTrue() { + uniqueClassList.add(JC_CHEMISTRY); + TuitionClass editedClass = new TuitionClassBuilder(JC_CHEMISTRY).withRate("69") + .withLocation("Punggol Central Waterway Point #2-14").build(); + assertTrue(uniqueClassList.contains(editedClass)); + } + + @Test + public void add_nullTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.add(null)); + } + + @Test + public void add_duplicateTuitionClass_throwsInvalidClassException() { + uniqueClassList.add(JC_CHEMISTRY); + assertThrows(OverlappingClassException.class, () -> uniqueClassList.add(JC_CHEMISTRY)); + } + + @Test + public void setClass_nullTargetTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.setClass(null, JC_CHEMISTRY)); + } + + @Test + public void setClass_nullEditedTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.setClass(JC_CHEMISTRY, null)); + } + + @Test + public void setClass_targetTuitionClassNotInList_throwsTuitionClassNotFoundException() { + assertThrows(TuitionClassNotFoundException.class, () -> uniqueClassList.setClass(JC_CHEMISTRY, JC_CHEMISTRY)); + } + + @Test + public void setClass_editedTuitionClassIsSameTuitionClass_success() { + uniqueClassList.add(JC_CHEMISTRY); + uniqueClassList.setClass(JC_CHEMISTRY, JC_CHEMISTRY); + UniqueClassList expectedUniqueClassList = new UniqueClassList(); + expectedUniqueClassList.add(JC_CHEMISTRY); + assertEquals(expectedUniqueClassList, uniqueClassList); + } + + @Test + public void setClass_editedTuitionClassHasSameIdentity_success() { + uniqueClassList.add(JC_CHEMISTRY); + TuitionClass editedChem = new TuitionClassBuilder(JC_CHEMISTRY).withRate("69") + .withLocation("Punggol Central Waterway Point #2-14").build(); + uniqueClassList.setClass(JC_CHEMISTRY, editedChem); + UniqueClassList expectedUniqueClassList = new UniqueClassList(); + expectedUniqueClassList.add(editedChem); + assertEquals(expectedUniqueClassList, uniqueClassList); + } + + @Test + public void setClass_editedTuitionClassHasDifferentIdentity_success() { + uniqueClassList.add(JC_CHEMISTRY); + uniqueClassList.setClass(JC_CHEMISTRY, JC_MATHS); + UniqueClassList expectedUniqueClassList = new UniqueClassList(); + expectedUniqueClassList.add(JC_MATHS); + assertEquals(expectedUniqueClassList, uniqueClassList); + } + + @Test + public void setClass_editedTuitionClassHasNonUniqueIdentity_throwsDuplicateClassException() { + uniqueClassList.add(JC_CHEMISTRY); + uniqueClassList.add(JC_MATHS); + assertThrows(DuplicateClassException.class, () -> uniqueClassList.setClass(JC_CHEMISTRY, JC_MATHS)); + } + + @Test + public void delete_nullTuitionClass_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.delete(null)); + } + + @Test + public void delete_tuitionClassDoesNotExist_throwsTuitionClassNotFoundException() { + assertThrows(TuitionClassNotFoundException.class, () -> uniqueClassList.delete(JC_CHEMISTRY)); + } + + @Test + public void delete_existingTuitionClass_removesTuitionClass() { + uniqueClassList.add(JC_CHEMISTRY); + uniqueClassList.delete(JC_CHEMISTRY); + UniqueClassList expectedUniqueClassList = new UniqueClassList(); + assertEquals(uniqueClassList, expectedUniqueClassList); + } + + @Test + public void setClasses_nullUniqueClassList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.setClasses((List) null)); + } + + @Test + public void setClasses_uniqueClassList_replacesOwnListWithProvidedList() { + uniqueClassList.add(JC_CHEMISTRY); + List tuitionClassList = new ArrayList<>(); + tuitionClassList.add(JC_MATHS); + uniqueClassList.setClasses(tuitionClassList); + UniqueClassList expectedUniqueClassList = new UniqueClassList(); + expectedUniqueClassList.add(JC_MATHS); + assertEquals(expectedUniqueClassList, uniqueClassList); + } + + @Test + public void setClasses_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueClassList.setClasses((List) null)); + } + + @Test + public void setClasses_listWithDuplicateTuitionClass_throwsDuplicateClassException() { + List listWithDuplicateTuitionClass = Arrays.asList(JC_CHEMISTRY, JC_CHEMISTRY); + assertThrows(DuplicateClassException.class, () -> uniqueClassList.setClasses(listWithDuplicateTuitionClass)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () + -> uniqueClassList.asUnmodifiableObservableList().remove(0)); + } + + @Test + public void equals_twoUniqueClassListSameClassesDifferentOrder_notEquals() { + uniqueClassList.add(JC_CHEMISTRY); + uniqueClassList.add(JC_MATHS); + UniqueClassList uniqueClassList2 = new UniqueClassList(); + uniqueClassList2.add(JC_MATHS); + uniqueClassList2.add(JC_CHEMISTRY); + assertFalse(uniqueClassList.equals(uniqueClassList2)); + } +} diff --git a/src/test/java/seedu/times/storage/JsonAdaptedNokTest.java b/src/test/java/seedu/times/storage/JsonAdaptedNokTest.java new file mode 100644 index 00000000000..8ba6a3e33ac --- /dev/null +++ b/src/test/java/seedu/times/storage/JsonAdaptedNokTest.java @@ -0,0 +1,92 @@ +package seedu.times.storage; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.times.storage.JsonAdaptedNok.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.BENSON; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; + +public class JsonAdaptedNokTest { + private static final String INVALID_NAME = "R@chel"; + private static final String INVALID_PHONE = "+651234"; + private static final String INVALID_ADDRESS = " "; + private static final String INVALID_EMAIL = "example.com"; + + private static final String VALID_NAME = BENSON.getName().toString(); + private static final String VALID_PHONE = BENSON.getPhone().toString(); + private static final String VALID_EMAIL = BENSON.getEmail().toString(); + private static final String VALID_ADDRESS = BENSON.getAddress().toString(); + private static final Nok VALID_NOK = BENSON.getNok(); + + @Test + public void toModelType_validPersonDetails_returnsPerson() throws Exception { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NOK); + assertEquals(VALID_NOK, nok.toModelType()); + } + + @Test + public void toModelType_invalidName_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS); + String expectedMessage = Name.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_nullName_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_invalidPhone_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS); + String expectedMessage = Phone.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_nullPhone_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_invalidEmail_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS); + String expectedMessage = Email.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_nullEmail_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_invalidAddress_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS); + String expectedMessage = Address.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + + @Test + public void toModelType_nullAddress_throwsIllegalValueException() { + JsonAdaptedNok nok = new JsonAdaptedNok(VALID_NAME, VALID_PHONE, VALID_EMAIL, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, nok::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/times/storage/JsonAdaptedStudentTest.java similarity index 52% rename from src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java rename to src/test/java/seedu/times/storage/JsonAdaptedStudentTest.java index 83b11331cdb..36138c92598 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/times/storage/JsonAdaptedStudentTest.java @@ -1,9 +1,10 @@ -package seedu.address.storage; +package seedu.times.storage; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.times.storage.JsonAdaptedStudent.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.BENSON; import java.util.ArrayList; import java.util.List; @@ -11,100 +12,115 @@ import org.junit.jupiter.api.Test; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; -public class JsonAdaptedPersonTest { +public class JsonAdaptedStudentTest { private static final String INVALID_NAME = "R@chel"; private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_TAG = " "; private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); private static final String VALID_EMAIL = BENSON.getEmail().toString(); private static final String VALID_ADDRESS = BENSON.getAddress().toString(); + private static final JsonAdaptedNok VALID_NOK = new JsonAdaptedNok(BENSON.getNok()); private static final List VALID_TAGS = BENSON.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList()); @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { - JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); - assertEquals(BENSON, person.toModelType()); + JsonAdaptedStudent student = new JsonAdaptedStudent(BENSON); + assertEquals(BENSON, student.toModelType()); + } + + @Test + public void toModelType_nullNok_success() throws Exception { + assertDoesNotThrow(() -> new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + null, VALID_TAGS)); } @Test public void toModelType_invalidName_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = + new JsonAdaptedStudent(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = new JsonAdaptedStudent(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_invalidPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = + new JsonAdaptedStudent(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = new JsonAdaptedStudent(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_invalidEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = + new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_invalidAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); + JsonAdaptedStudent student = + new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, + VALID_NOK, VALID_TAGS); String expectedMessage = Address.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); + JsonAdaptedStudent student = new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, + VALID_NOK, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, student::toModelType); } @Test public void toModelType_invalidTags_throwsIllegalValueException() { List invalidTags = new ArrayList<>(VALID_TAGS); invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); - assertThrows(IllegalValueException.class, person::toModelType); + JsonAdaptedStudent student = + new JsonAdaptedStudent(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_NOK, invalidTags); + assertThrows(IllegalValueException.class, student::toModelType); } - } diff --git a/src/test/java/seedu/times/storage/JsonAdaptedTuitionClassTest.java b/src/test/java/seedu/times/storage/JsonAdaptedTuitionClassTest.java new file mode 100644 index 00000000000..995f0d641ca --- /dev/null +++ b/src/test/java/seedu/times/storage/JsonAdaptedTuitionClassTest.java @@ -0,0 +1,110 @@ +package seedu.times.storage; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.times.storage.JsonAdaptedTuitionClass.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.IB_PHYSICS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; + +public class JsonAdaptedTuitionClassTest { + private static final String INVALID_CLASS_NAME = " "; + private static final String INVALID_CLASS_TIMING = "MON 11:00-10:00"; + private static final String INVALID_RATE = "Sixty"; + private static final String INVALID_LOCATION = " "; + private static final List INVALID_STUDENT_NAME_LIST = new ArrayList<>(Arrays.asList(" ")); + + private static final String VALID_CLASS_NAME = IB_PHYSICS.getClassName().toString(); + private static final String VALID_CLASS_TIMING = IB_PHYSICS.getClassTiming().toString(); + private static final String VALID_RATE = IB_PHYSICS.getRate().toString(); + private static final String VALID_LOCATION = IB_PHYSICS.getLocation().toString(); + private static final List VALID_EMPTY_STUDENT_NAME_LIST = new ArrayList<>(); + + @Test + public void toModelType_validTuitionClassDetails_returnsPerson() throws Exception { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(IB_PHYSICS); + assertEquals(IB_PHYSICS, tuitionClass.toModelType()); + } + + @Test + public void toModelType_emptyStudentList_success() throws Exception { + assertDoesNotThrow(() -> new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, VALID_CLASS_NAME, VALID_RATE, + VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST)); + } + + @Test + public void toModelType_invalidClassTiming_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(INVALID_CLASS_TIMING, VALID_CLASS_NAME, + VALID_RATE, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = ClassTiming.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_nullClassTiming_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(null, VALID_CLASS_NAME, + VALID_RATE, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, ClassTiming.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_invalidClassName_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, INVALID_CLASS_NAME, + VALID_RATE, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = ClassName.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_nullClassName_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, null, + VALID_RATE, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, ClassName.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_invalidRate_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, VALID_CLASS_NAME, + INVALID_RATE, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = Rate.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_nullRate_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, VALID_CLASS_NAME, + null, VALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Rate.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_invalidLocation_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, VALID_CLASS_NAME, + VALID_RATE, INVALID_LOCATION, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = Location.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + + @Test + public void toModelType_nullLocation_throwsIllegalValueException() { + JsonAdaptedTuitionClass tuitionClass = new JsonAdaptedTuitionClass(VALID_CLASS_TIMING, VALID_CLASS_NAME, + VALID_RATE, null, VALID_EMPTY_STUDENT_NAME_LIST); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Location.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, tuitionClass::toModelType); + } + +} diff --git a/src/test/java/seedu/times/storage/JsonSerializableTimesTableTest.java b/src/test/java/seedu/times/storage/JsonSerializableTimesTableTest.java new file mode 100644 index 00000000000..cbfe69b926f --- /dev/null +++ b/src/test/java/seedu/times/storage/JsonSerializableTimesTableTest.java @@ -0,0 +1,47 @@ +package seedu.times.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.times.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import seedu.times.commons.exceptions.IllegalValueException; +import seedu.times.commons.util.JsonUtil; +import seedu.times.model.TimesTable; +import seedu.times.testutil.TypicalTimestable; + +public class JsonSerializableTimesTableTest { + + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableTimesTableTest"); + private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsTimesTable.json"); + private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonTimesTable.json"); + private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonTimesTable.json"); + + @Test + public void toModelType_typicalPersonsFile_success() throws Exception { + JsonSerializableTimesTable dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, + JsonSerializableTimesTable.class).get(); + TimesTable timesTableFromFile = dataFromFile.toModelType(); + TimesTable typicalPersonsTimesTable = TypicalTimestable.getTypicalTimesTable(); + assertEquals(timesTableFromFile, typicalPersonsTimesTable); + } + + @Test + public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { + JsonSerializableTimesTable dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, + JsonSerializableTimesTable.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { + JsonSerializableTimesTable dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, + JsonSerializableTimesTable.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableTimesTable.MESSAGE_DUPLICATE_PERSON, + dataFromFile::toModelType); + } + +} diff --git a/src/test/java/seedu/times/storage/JsonTimesTableStorageTest.java b/src/test/java/seedu/times/storage/JsonTimesTableStorageTest.java new file mode 100644 index 00000000000..e8914cd4e4a --- /dev/null +++ b/src/test/java/seedu/times/storage/JsonTimesTableStorageTest.java @@ -0,0 +1,110 @@ +package seedu.times.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.times.testutil.Assert.assertThrows; +import static seedu.times.testutil.TypicalTimestable.ALICE; +import static seedu.times.testutil.TypicalTimestable.HOON; +import static seedu.times.testutil.TypicalTimestable.IDA; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.TimesTable; + +public class JsonTimesTableStorageTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonTimesTableStorageTest"); + + @TempDir + public Path testFolder; + + @Test + public void readTimesTable_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> readTimesTable(null)); + } + + private java.util.Optional readTimesTable(String filePath) throws Exception { + return new JsonTimesTableStorage(Paths.get(filePath)).readTimesTable(addToTestDataPathIfNotNull(filePath)); + } + + private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readTimesTable("NonExistentFile.json").isPresent()); + } + + @Test + public void read_notJsonFormat_exceptionThrown() { + assertThrows(DataConversionException.class, () -> readTimesTable("notJsonFormatTimesTable.json")); + } + + @Test + public void readTimesTable_invalidPersonTimesTable_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readTimesTable("invalidPersonTimesTable.json")); + } + + @Test + public void readTimesTable_invalidAndValidPersonTimesTable_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readTimesTable("invalidAndValidPersonTimesTable.json")); + } + + @Test + public void readAndSaveTimesTable_allInOrder_success() throws Exception { + Path filePath = testFolder.resolve("TempTimesTable.json"); + TimesTable original = getTypicalTimesTable(); + JsonTimesTableStorage jsonTimesTableStorage = new JsonTimesTableStorage(filePath); + + // Save in new file and read back + jsonTimesTableStorage.saveTimesTable(original, filePath); + ReadOnlyTimesTable readBack = jsonTimesTableStorage.readTimesTable(filePath).get(); + assertEquals(original, new TimesTable(readBack)); + + // Modify data, overwrite exiting file, and read back + original.addStudent(HOON); + original.removePerson(ALICE); + jsonTimesTableStorage.saveTimesTable(original, filePath); + readBack = jsonTimesTableStorage.readTimesTable(filePath).get(); + assertEquals(original, new TimesTable(readBack)); + + // Save and read without specifying file path + original.addStudent(IDA); + jsonTimesTableStorage.saveTimesTable(original); // file path not specified + readBack = jsonTimesTableStorage.readTimesTable().get(); // file path not specified + assertEquals(original, new TimesTable(readBack)); + + } + + @Test + public void saveTimesTable_nullTimesTable_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveTimesTable(null, "SomeFile.json")); + } + + /** + * Saves {@code timestable} at the specified {@code filePath}. + */ + private void saveTimesTable(ReadOnlyTimesTable timesTable, String filePath) { + try { + new JsonTimesTableStorage(Paths.get(filePath)) + .saveTimesTable(timesTable, addToTestDataPathIfNotNull(filePath)); + } catch (IOException ioe) { + throw new AssertionError("There should not be an error writing to the file.", ioe); + } + } + + @Test + public void saveTimesTable_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveTimesTable(new TimesTable(), null)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/times/storage/JsonUserPrefsStorageTest.java similarity index 93% rename from src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java rename to src/test/java/seedu/times/storage/JsonUserPrefsStorageTest.java index 16f33f4a6bb..205d7c21bf6 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/times/storage/JsonUserPrefsStorageTest.java @@ -1,8 +1,8 @@ -package seedu.address.storage; +package seedu.times.storage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import java.io.IOException; import java.nio.file.Path; @@ -12,9 +12,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +import seedu.times.commons.core.GuiSettings; +import seedu.times.commons.exceptions.DataConversionException; +import seedu.times.model.UserPrefs; public class JsonUserPrefsStorageTest { @@ -73,7 +73,7 @@ public void readUserPrefs_extraValuesInFile_extraValuesIgnored() throws DataConv private UserPrefs getTypicalUserPrefs() { UserPrefs userPrefs = new UserPrefs(); userPrefs.setGuiSettings(new GuiSettings(1000, 500, 300, 100)); - userPrefs.setAddressBookFilePath(Paths.get("addressbook.json")); + userPrefs.setTimesTableFilePath(Paths.get("timestable.json")); return userPrefs; } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/times/storage/StorageManagerTest.java similarity index 60% rename from src/test/java/seedu/address/storage/StorageManagerTest.java rename to src/test/java/seedu/times/storage/StorageManagerTest.java index 99a16548970..83dd7abd714 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/times/storage/StorageManagerTest.java @@ -1,8 +1,8 @@ -package seedu.address.storage; +package seedu.times.storage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.times.testutil.TypicalTimestable.getTypicalTimesTable; import java.nio.file.Path; @@ -10,10 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; +import seedu.times.commons.core.GuiSettings; +import seedu.times.model.ReadOnlyTimesTable; +import seedu.times.model.TimesTable; +import seedu.times.model.UserPrefs; public class StorageManagerTest { @@ -24,9 +24,9 @@ public class StorageManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab")); + JsonTimesTableStorage timesTableStorage = new JsonTimesTableStorage(getTempFilePath("ab")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); - storageManager = new StorageManager(addressBookStorage, userPrefsStorage); + storageManager = new StorageManager(timesTableStorage, userPrefsStorage); } private Path getTempFilePath(String fileName) { @@ -48,21 +48,21 @@ public void prefsReadSave() throws Exception { } @Test - public void addressBookReadSave() throws Exception { + public void timesTableReadSave() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonAddressBookStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. + * {@link JsonTimesTableStorage} class. + * More extensive testing of UserPref saving/reading is done in {@link JsonTimesTableStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); - storageManager.saveAddressBook(original); - ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); - assertEquals(original, new AddressBook(retrieved)); + TimesTable original = getTypicalTimesTable(); + storageManager.saveTimesTable(original); + ReadOnlyTimesTable retrieved = storageManager.readTimesTable().get(); + assertEquals(original, new TimesTable(retrieved)); } @Test - public void getAddressBookFilePath() { - assertNotNull(storageManager.getAddressBookFilePath()); + public void getTimesTableFilePath() { + assertNotNull(storageManager.getTimesTableFilePath()); } } diff --git a/src/test/java/seedu/address/testutil/Assert.java b/src/test/java/seedu/times/testutil/Assert.java similarity index 97% rename from src/test/java/seedu/address/testutil/Assert.java rename to src/test/java/seedu/times/testutil/Assert.java index 9863093bd6e..c710b49b471 100644 --- a/src/test/java/seedu/address/testutil/Assert.java +++ b/src/test/java/seedu/times/testutil/Assert.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.times.testutil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; diff --git a/src/test/java/seedu/times/testutil/EditClassDescriptorBuilder.java b/src/test/java/seedu/times/testutil/EditClassDescriptorBuilder.java new file mode 100644 index 00000000000..5a47175a21d --- /dev/null +++ b/src/test/java/seedu/times/testutil/EditClassDescriptorBuilder.java @@ -0,0 +1,100 @@ +package seedu.times.testutil; + +import seedu.times.logic.commands.classcommands.EditClassCommand.EditClassDescriptor; +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; + +public class EditClassDescriptorBuilder { + private EditClassDescriptor descriptor; + + public EditClassDescriptorBuilder() { + descriptor = new EditClassDescriptor(); + } + + public EditClassDescriptorBuilder(EditClassDescriptor descriptor) { + this.descriptor = new EditClassDescriptor(descriptor); + } + + /** + * Constructs an EditClassDescriptorBuilder with the information of the TuitionClass object. + * + * @param tuitionClass TuitionClass object to build the EditClassDescriptorBuilder with. + */ + public EditClassDescriptorBuilder(TuitionClass tuitionClass) { + descriptor = new EditClassDescriptor(); + + descriptor.setClassName(tuitionClass.getClassName()); + descriptor.setClassTiming(tuitionClass.getClassTiming()); + descriptor.setRate(tuitionClass.getRate()); + descriptor.setLocation(tuitionClass.getLocation()); + descriptor.setStudentList(tuitionClass.getStudentList()); + } + + /** + * Sets the class name of the EditClassDescriptorBuilder object to the given class name. + * + * @param className Class name to be set to. + * @return EditClassDescriptorBuilder object with the class name set to the given class name. + */ + public EditClassDescriptorBuilder withClassName(String className) { + descriptor.setClassName(new ClassName(className)); + return this; + } + + /** + * Sets the class timing of the EditClassDescriptorBuilder object to the given class timing. + * + * @param classTiming Class timing to be set to. + * @return EditClassDescriptorBuilder object with the class timing set to the given class timing. + */ + public EditClassDescriptorBuilder withClassTiming(String classTiming) { + descriptor.setClassTiming(new ClassTiming(classTiming)); + return this; + } + + /** + * Sets the rate of the EditClassDescriptorBuilder object to the given rate. + * + * @param rate Rate to be set to. + * @return EditClassDescriptorBuilder object with the rate set to the given rate. + */ + public EditClassDescriptorBuilder withRate(String rate) { + descriptor.setRate(new Rate(rate)); + return this; + } + + /** + * Sets the location of the EditClassDescriptorBuilder object to the given location. + * + * @param location Location to be set to. + * @return EditClassDescriptorBuilder object with the location set to the given location. + */ + public EditClassDescriptorBuilder withLocation(String location) { + descriptor.setLocation(new Location(location)); + return this; + } + + /** + * Sets the student list of the EditClassDescriptorBuilder object to the given student list. + * + * @param studentList Student list to be set to. + * @return EditClassDescriptorBuilder object with the student list set to the given student list. + */ + public EditClassDescriptorBuilder withStudentList(String... studentList) { + descriptor.setStudentList(new StudentNameList(studentList)); + return this; + } + + /** + * Builds the EditClassDescriptor with the information stored in the EditClassDescriptorBuilder object. + * + * @return EditClassDescriptor built with the information stored in the EditClassDescriptorBuilder object. + */ + public EditClassDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/times/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/times/testutil/EditPersonDescriptorBuilder.java new file mode 100644 index 00000000000..52477f9b290 --- /dev/null +++ b/src/test/java/seedu/times/testutil/EditPersonDescriptorBuilder.java @@ -0,0 +1,126 @@ +package seedu.times.testutil; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import seedu.times.logic.commands.EditCommand; +import seedu.times.logic.commands.EditCommand.EditStudentDescriptor; +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; + +/** + * A utility class to help with building EditPersonDescriptor objects. + */ +public class EditPersonDescriptorBuilder { + + private EditStudentDescriptor descriptor; + + public EditPersonDescriptorBuilder() { + descriptor = new EditStudentDescriptor(); + } + + public EditPersonDescriptorBuilder(EditCommand.EditStudentDescriptor descriptor) { + this.descriptor = new EditStudentDescriptor(descriptor); + } + + /** + * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details + */ + public EditPersonDescriptorBuilder(Student student) { + descriptor = new EditCommand.EditStudentDescriptor(); + + descriptor.setName(student.getName()); + descriptor.setPhone(student.getPhone()); + descriptor.setEmail(student.getEmail()); + descriptor.setAddress(student.getAddress()); + descriptor.setTags(student.getTags()); + + descriptor.setNokName(student.getNok().getName()); + descriptor.setNokPhone(student.getNok().getPhone()); + descriptor.setNokEmail(student.getNok().getEmail()); + descriptor.setNokAddress(student.getNok().getAddress()); + } + + /** + * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withName(String name) { + descriptor.setName(new Name(name)); + return this; + } + + /** + * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withPhone(String phone) { + descriptor.setPhone(new Phone(phone)); + return this; + } + + /** + * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withEmail(String email) { + descriptor.setEmail(new Email(email)); + return this; + } + + /** + * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withAddress(String address) { + descriptor.setAddress(new Address(address)); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} + * that we are building. + */ + public EditPersonDescriptorBuilder withTags(String... tags) { + Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); + descriptor.setTags(tagSet); + return this; + } + + /** + * Sets the {@code NokName} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withNokName(String name) { + descriptor.setNokName(new Name(name)); + return this; + } + + /** + * Sets the {@code NokPhone} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withNokPhone(String phone) { + descriptor.setNokPhone(new Phone(phone)); + return this; + } + + /** + * Sets the {@code NokEmail} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withNokEmail(String email) { + descriptor.setNokEmail(new Email(email)); + return this; + } + + /** + * Sets the {@code NokAddress} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withNokAddress(String address) { + descriptor.setNokAddress(new Address(address)); + return this; + } + + public EditStudentDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/times/testutil/PersonBuilder.java b/src/test/java/seedu/times/testutil/PersonBuilder.java new file mode 100644 index 00000000000..6cd9fd00042 --- /dev/null +++ b/src/test/java/seedu/times/testutil/PersonBuilder.java @@ -0,0 +1,153 @@ +package seedu.times.testutil; + +import java.util.HashSet; +import java.util.Set; + +import seedu.times.model.person.Address; +import seedu.times.model.person.Email; +import seedu.times.model.person.Name; +import seedu.times.model.person.Nok; +import seedu.times.model.person.Phone; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; +import seedu.times.model.util.SampleDataUtil; + +/** + * A utility class to help with building Person objects. + */ +public class PersonBuilder { + + public static final String DEFAULT_NAME = "Amy Bee"; + public static final String DEFAULT_PHONE = "85355255"; + public static final String DEFAULT_EMAIL = "amy@gmail.com"; + public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + public static final String DEFAULT_NOK_NAME = "Zhenglin Ong"; + public static final String DEFAULT_NOK_PHONE = "97762839"; + public static final String DEFAULT_NOK_EMAIL = "zhenglin@gmail.com"; + public static final String DEFAULT_NOK_ADDRESS = "345, Clementi Ave 6, #02-141"; + + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set tags; + + private Name nokName; + private Phone nokPhone; + private Email nokEmail; + private Address nokAddress; + + /** + * Creates a {@code PersonBuilder} with the default details. + */ + public PersonBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + tags = new HashSet<>(); + + nokName = new Name(DEFAULT_NOK_NAME); + nokPhone = new Phone(DEFAULT_NOK_PHONE); + nokEmail = new Email(DEFAULT_NOK_EMAIL); + nokAddress = new Address(DEFAULT_NOK_ADDRESS); + } + + /** + * Initializes the PersonBuilder with the data of {@code personToCopy}. + */ + public PersonBuilder(Student studentToCopy) { + name = studentToCopy.getName(); + phone = studentToCopy.getPhone(); + email = studentToCopy.getEmail(); + address = studentToCopy.getAddress(); + tags = new HashSet<>(studentToCopy.getTags()); + nokName = studentToCopy.getNok().getName(); + nokPhone = studentToCopy.getNok().getPhone(); + nokEmail = studentToCopy.getNok().getEmail(); + nokAddress = studentToCopy.getNok().getAddress(); + } + + /** + * Sets the {@code Name} of the {@code Person} that we are building. + */ + public PersonBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. + */ + public PersonBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + /** + * Sets the {@code Address} of the {@code Person} that we are building. + */ + public PersonBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Person} that we are building. + */ + public PersonBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Person} that we are building. + */ + public PersonBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + /** + * Sets the {@code NokName} of the {@code EditPersonDescriptor} that we are building. + */ + public PersonBuilder withNokName(String name) { + this.nokName = new Name(name); + return this; + } + + /** + * Sets the {@code NokPhone} of the {@code EditPersonDescriptor} that we are building. + */ + public PersonBuilder withNokPhone(String phone) { + this.nokPhone = new Phone(phone); + return this; + } + + /** + * Sets the {@code NokEmail} of the {@code EditPersonDescriptor} that we are building. + */ + public PersonBuilder withNokEmail(String email) { + this.nokEmail = new Email(email); + return this; + } + + /** + * Sets the {@code NokAddress} of the {@code EditPersonDescriptor} that we are building. + */ + public PersonBuilder withNokAddress(String address) { + this.nokAddress = new Address(address); + return this; + } + + /** + * Builds student with all details included in the builder. + * + * @return Student with all the deatils included in the builder. + */ + public Student build() { + Nok nok = new Nok(nokName, nokPhone, nokEmail, nokAddress); + return new Student(name, phone, email, address, nok, tags); + } + +} diff --git a/src/test/java/seedu/times/testutil/PersonUtil.java b/src/test/java/seedu/times/testutil/PersonUtil.java new file mode 100644 index 00000000000..be67146872a --- /dev/null +++ b/src/test/java/seedu/times/testutil/PersonUtil.java @@ -0,0 +1,81 @@ +package seedu.times.testutil; + +import static seedu.times.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.times.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.times.logic.parser.CliSyntax.PREFIX_NOK; +import static seedu.times.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.times.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; + +import seedu.times.logic.commands.AddCommand; +import seedu.times.logic.commands.EditCommand.EditStudentDescriptor; +import seedu.times.model.person.Student; +import seedu.times.model.tag.Tag; + +/** + * A utility class for Person. + */ +public class PersonUtil { + + /** + * Returns an add command string for adding the {@code person}. + */ + public static String getAddCommand(Student student) { + return AddCommand.COMMAND_WORD + " " + getPersonDetails(student); + } + + /** + * Returns the part of command string for the given {@code person}'s details. + */ + public static String getPersonDetails(Student student) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_NAME + student.getName().fullName + " "); + sb.append(PREFIX_PHONE + student.getPhone().value + " "); + sb.append(PREFIX_EMAIL + student.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + student.getAddress().value + " "); + student.getTags().stream().forEach(s -> sb.append(PREFIX_TAG + s.tagName + " ")); + sb.append(PREFIX_NOK + " "); + sb.append(PREFIX_NAME + student.getNok().getName().fullName + " "); + sb.append(PREFIX_PHONE + student.getNok().getPhone().value + " "); + sb.append(PREFIX_EMAIL + student.getNok().getEmail().value + " "); + sb.append(PREFIX_ADDRESS + student.getNok().getAddress().value + " "); + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditPersonDescriptor}'s details. + */ + public static String getEditPersonDescriptorDetails(EditStudentDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); + descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); + descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); + descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); + //if (descriptor.getTuitionClasses().isPresent()) { + // List tuitionClasses = descriptor.getTuitionClasses().get(); + // if(tuitionClasses.isEmpty()) { + // sb.append() + // } + //} + if (descriptor.getTags().isPresent()) { + Set tags = descriptor.getTags().get(); + if (tags.isEmpty()) { + sb.append(PREFIX_TAG); + } else { + tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); + } + } + if (descriptor.getNokName().isPresent() || descriptor.getNokAddress().isPresent() + || descriptor.getNokEmail().isPresent() || descriptor.getNokPhone().isPresent()) { + sb.append(PREFIX_NOK).append(" "); + } + descriptor.getNokName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); + descriptor.getNokPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); + descriptor.getNokEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); + descriptor.getNokAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); + + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/SerializableTestClass.java b/src/test/java/seedu/times/testutil/SerializableTestClass.java similarity index 98% rename from src/test/java/seedu/address/testutil/SerializableTestClass.java rename to src/test/java/seedu/times/testutil/SerializableTestClass.java index f5a66340489..fcf18c52c44 100644 --- a/src/test/java/seedu/address/testutil/SerializableTestClass.java +++ b/src/test/java/seedu/times/testutil/SerializableTestClass.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.times.testutil; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/times/testutil/TestUtil.java similarity index 50% rename from src/test/java/seedu/address/testutil/TestUtil.java rename to src/test/java/seedu/times/testutil/TestUtil.java index 896d103eb0b..f18bd74fc45 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/times/testutil/TestUtil.java @@ -1,13 +1,17 @@ -package seedu.address.testutil; +package seedu.times.testutil; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.times.commons.core.index.Index; +import seedu.times.model.Model; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; /** * A utility class for test cases. @@ -36,20 +40,33 @@ public static Path getFilePathInSandboxFolder(String fileName) { * Returns the middle index of the person in the {@code model}'s person list. */ public static Index getMidIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size() / 2); + return Index.fromOneBased(model.getFilteredStudentList().size() / 2); } /** * Returns the last index of the person in the {@code model}'s person list. */ public static Index getLastIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size()); + return Index.fromOneBased(model.getFilteredStudentList().size()); } /** - * Returns the person in the {@code model}'s person list at {@code index}. + * Returns the Student in the {@code model}'s student list at {@code index} - 1. */ - public static Person getPerson(Model model, Index index) { - return model.getFilteredPersonList().get(index.getZeroBased()); + public static Student getStudentOneBased(Model model, int index) { + return model.getFilteredStudentList().get(index - 1); + } + + /** + * Returns the tuition class in the {@code model}'s student list at {@code index} - 1. + */ + public static TuitionClass getClassOneBased(Model model, int index) { + return model.getFilteredTuitionClassList().get(index - 1); + } + + public static List getIndexList(int... indices) { + List res = new ArrayList<>(); + Arrays.stream(indices).forEach(index -> res.add(Index.fromOneBased(index))); + return res; } } diff --git a/src/test/java/seedu/times/testutil/TimesTableBuilder.java b/src/test/java/seedu/times/testutil/TimesTableBuilder.java new file mode 100644 index 00000000000..195a026c60e --- /dev/null +++ b/src/test/java/seedu/times/testutil/TimesTableBuilder.java @@ -0,0 +1,43 @@ +package seedu.times.testutil; + +import seedu.times.model.TimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * A utility class to help with building TimesTable objects. + * Example usage:
+ * {@code TimesTable ab = new TimesTableBuilder().withPerson("John", "Doe").build();} + */ +public class TimesTableBuilder { + + private TimesTable timesTable; + + public TimesTableBuilder() { + timesTable = new TimesTable(); + } + + public TimesTableBuilder(TimesTable timesTable) { + this.timesTable = timesTable; + } + + /** + * Adds a new {@code Person} to the {@code TimesTable} that we are building. + */ + public TimesTableBuilder withStudent(Student student) { + timesTable.addStudent(student); + return this; + } + + /** + * Adds a new {@code TuitionClass} to the {@code TimesTable} that we are building. + */ + public TimesTableBuilder withTuitionClass(TuitionClass tuitionClass) { + timesTable.addTuitionClass(tuitionClass); + return this; + } + + public TimesTable build() { + return timesTable; + } +} diff --git a/src/test/java/seedu/times/testutil/TuitionClassBuilder.java b/src/test/java/seedu/times/testutil/TuitionClassBuilder.java new file mode 100644 index 00000000000..afaa9458722 --- /dev/null +++ b/src/test/java/seedu/times/testutil/TuitionClassBuilder.java @@ -0,0 +1,111 @@ +package seedu.times.testutil; + +import seedu.times.model.tuitionclass.ClassName; +import seedu.times.model.tuitionclass.ClassTiming; +import seedu.times.model.tuitionclass.Location; +import seedu.times.model.tuitionclass.Rate; +import seedu.times.model.tuitionclass.StudentNameList; +import seedu.times.model.tuitionclass.TuitionClass; + +public class TuitionClassBuilder { + + public static final String DEFAULT_CLASS_NAME = "O Levels English"; + public static final String DEFAULT_RATE = "51"; + public static final String DEFAULT_LOCATION = "Tuas Blk 111"; + public static final String DEFAULT_CLASS_TIMING = "sun 16:00-17:00"; + public static final String[] DEFAULT_NAME_LIST = {"Alice", "Benson", "Carl"}; + + private ClassName className; + private ClassTiming classTiming; + private Rate rate; + private Location location; + private StudentNameList studentNameList; + + /** + * Constructs a new TuitionClassBuilder with the default information. + */ + public TuitionClassBuilder() { + className = new ClassName(DEFAULT_CLASS_NAME); + classTiming = new ClassTiming(DEFAULT_CLASS_TIMING); + rate = new Rate(DEFAULT_RATE); + location = new Location(DEFAULT_LOCATION); + studentNameList = new StudentNameList(DEFAULT_NAME_LIST); + } + + /** + * Constructs a new TuitionClassBuilder with the same information as the classToCopy. + * + * @param classToCopy TuitionClass to copy the information from. + */ + public TuitionClassBuilder(TuitionClass classToCopy) { + className = classToCopy.getClassName(); + classTiming = classToCopy.getClassTiming(); + rate = classToCopy.getRate(); + location = classToCopy.getLocation(); + studentNameList = classToCopy.getStudentList(); + } + + /** + * Changes the class name of the TuitionClassBuilder to the given name. + * + * @param className Name to be changed to. + * @return TuitionClassBuilder with the new name. + */ + public TuitionClassBuilder withClassName(String className) { + this.className = new ClassName(className); + return this; + } + + /** + * Changes the class timing of the TuitionClassBuilder to the given class timing. + * + * @param classTiming Class timing to be changed to. + * @return TuitionClassBuilder with the new class timing. + */ + public TuitionClassBuilder withClassTiming(String classTiming) { + this.classTiming = new ClassTiming(classTiming); + return this; + } + + /** + * Changes the rate of the TuitionClassBuilder to the given rate. + * + * @param rate Rate to be changed to. + * @return TuitionClassBuilder with the new rate. + */ + public TuitionClassBuilder withRate(String rate) { + this.rate = new Rate(rate); + return this; + } + + /** + * Changes the location of the TuitionClassBuilder to the given location. + * + * @param location Location to be changed to. + * @return TuitionClassBuilder with the new location. + */ + public TuitionClassBuilder withLocation(String location) { + this.location = new Location(location); + return this; + } + + /** + * Changes the student list of the TuitionClassBuilder to the given student list. + * + * @param studentList Student list to be changed to. + * @return TuitionClassBuilder with the new student list. + */ + public TuitionClassBuilder withStudentList(String... studentList) { + this.studentNameList = new StudentNameList(studentList); + return this; + } + + /** + * Builds a TuitionClass with the information of the TuitionClassBuilder object. + * + * @return TuitionClass with the information of the TuitionClassBuilder. + */ + public TuitionClass build() { + return new TuitionClass(className, classTiming, location, rate, studentNameList); + } +} diff --git a/src/test/java/seedu/times/testutil/TypicalIndexes.java b/src/test/java/seedu/times/testutil/TypicalIndexes.java new file mode 100644 index 00000000000..4e749510ef9 --- /dev/null +++ b/src/test/java/seedu/times/testutil/TypicalIndexes.java @@ -0,0 +1,12 @@ +package seedu.times.testutil; + +import seedu.times.commons.core.index.Index; + +/** + * A utility class containing a list of {@code Index} objects to be used in tests. + */ +public class TypicalIndexes { + public static final Index INDEX_FIRST = Index.fromOneBased(1); + public static final Index INDEX_SECOND = Index.fromOneBased(2); + public static final Index INDEX_THIRD = Index.fromOneBased(3); +} diff --git a/src/test/java/seedu/times/testutil/TypicalTimestable.java b/src/test/java/seedu/times/testutil/TypicalTimestable.java new file mode 100644 index 00000000000..b4950fbb2d9 --- /dev/null +++ b/src/test/java/seedu/times/testutil/TypicalTimestable.java @@ -0,0 +1,220 @@ +package seedu.times.testutil; + +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_ADDRESS_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSNAME_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_CLASSTIMING_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_EMAIL_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_LOCATION_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_NAME_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.times.logic.commands.CommandTestUtil.VALID_PHONE_NOK; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_RATE_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_STUDENTLIST_IB_MATHS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_STUDENTLIST_IB_PHYSICS; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.times.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.times.model.TimesTable; +import seedu.times.model.person.Student; +import seedu.times.model.tuitionclass.TuitionClass; + +/** + * A utility class containing a list of {@code Person} objects to be used in tests. + */ +public class TypicalTimestable { + + ///Typical Students + public static final Student ALICE = new PersonBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("94351253") + .withNokName("Long Chai Boon") + .withNokAddress("325, Clementi State 3, #40-32") + .withNokEmail("longchatbooon@gmail.com").withNokPhone("67785914") + .withTags("Maths").build(); + public static final Student BENSON = new PersonBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withNokName("Short Chai Boon") + .withNokAddress("325, West State 3, #60-12") + .withNokEmail("hahiihi@gmail.com").withNokPhone("87759868") + .withTags("Physics").build(); + public static final Student CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("Campbell Road Ave 2, #11") + .withNokName("Jamie Kurz") + .withNokAddress("325, Bat Cave 3, #10-23") + .withNokEmail("dontcallmeillcome@gmail.com").withNokPhone("62212222") + .build(); + public static final Student DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street") + .withNokName("Bad Chai Boon") + .withNokAddress("1, Happy Sad 3, #10-12") + .withNokEmail("happyorsadyouchoose@gmail.com").withNokPhone("88888888") + .withTags("Maths").build(); + public static final Student ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave") + .withNokName("Bernard Wan") + .withNokAddress("222, Berkeys State 4, #11-32") + .withNokEmail("bernard@gmail.com").withNokPhone("67785314").build(); + public static final Student FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo") + .withNokName("Ong Lin Zhen") + .withNokAddress("323, Lin Estate 3, #44-31") + .withNokEmail("zhenngggii@gmail.com").withNokPhone("67382344") + .build(); + public static final Student GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street") + .withNokName("Hehe Chai Red") + .withNokAddress("999, Estate State 4, #10-32") + .withNokEmail("salmon@gmail.com").withNokPhone("67111111") + .build(); + + // Manually added + public static final Student HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india") + .build(); + public static final Student IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Student AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withNokName(VALID_NAME_NOK).withNokPhone(VALID_PHONE_NOK).withNokAddress(VALID_ADDRESS_NOK) + .withNokEmail(VALID_EMAIL_NOK).build(); + public static final Student BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withNokName(VALID_NAME_NOK).withNokPhone(VALID_PHONE_NOK).withNokAddress(VALID_ADDRESS_NOK) + .withNokEmail(VALID_EMAIL_NOK) + .build(); + + + // Typical Classes + public static final String JC_PHYSICS_DAY = "Mon"; + public static final String JC_PHYSICS_TIME = "10:00-12:00"; + public static final String JC_PHYSICS_CLASS_TIMING = JC_PHYSICS_DAY + " " + JC_PHYSICS_TIME; + public static final String SEC_PHYSICS_DAY = "Tue"; + public static final String SEC_PHYSICS_TIME = "11:00-13:00"; + public static final String SEC_PHYSICS_CLASS_TIMING = SEC_PHYSICS_DAY + " " + SEC_PHYSICS_TIME; + public static final String JC_MATHS_DAY = "Wed"; + public static final String JC_MATHS_TIME = "15:00-17:00"; + public static final String JC_MATHS_CLASS_TIMING = JC_MATHS_DAY + " " + JC_MATHS_TIME; + public static final String SEC_MATHS_DAY = "Thu"; + public static final String SEC_MATHS_TIME = "10:00-12:00"; + public static final String SEC_MATHS_CLASS_TIMING = SEC_MATHS_DAY + " " + SEC_MATHS_TIME; + public static final String JC_CHEMISTRY_DAY = "Fri"; + public static final String JC_CHEMISTRY_TIME = "13:00-16:00"; + public static final String JC_CHEMISTRY_CLASS_TIMING = JC_CHEMISTRY_DAY + " " + JC_CHEMISTRY_TIME; + public static final String SEC_CHEMISTRY_DAY = "Fri"; + public static final String SEC_CHEMISTRY_TIME = "17:00-18:00"; + public static final String SEC_CHEMISTRY_CLASS_TIMING = SEC_CHEMISTRY_DAY + " " + SEC_CHEMISTRY_TIME; + + public static final TuitionClass JC_PHYSICS = new TuitionClassBuilder().withClassName("JC Physics") + .withClassTiming(JC_PHYSICS_CLASS_TIMING).withRate("70").withLocation("Jaycee Tuition Center Nex") + .withStudentList(VALID_STUDENTLIST_IB_PHYSICS).build(); + public static final TuitionClass SEC_PHYSICS = new TuitionClassBuilder().withClassName("Sec 4 Physics") + .withClassTiming(SEC_PHYSICS_CLASS_TIMING).withRate("77").withLocation("Learning Lab Orchard") + .withStudentList(VALID_STUDENTLIST_IB_MATHS).build(); + public static final TuitionClass JC_MATHS = new TuitionClassBuilder().withClassName("JC Maths") + .withClassTiming(JC_MATHS_CLASS_TIMING).withRate("55") + .withLocation("Bukit Merah Block 614 #01-330").build(); + public static final TuitionClass SEC_MATHS = new TuitionClassBuilder().withClassName("Sec 4 Maths") + .withClassTiming(SEC_MATHS_CLASS_TIMING).withRate("60") + .withLocation("Merlion Tuition Center Kovan").build(); + public static final TuitionClass JC_CHEMISTRY = new TuitionClassBuilder().withClassName("JC Chemistry") + .withClassTiming(JC_CHEMISTRY_CLASS_TIMING).withRate("50").withLocation("Hougang Blk 313 #11-394").build(); + public static final TuitionClass SEC_CHEMISTRY = new TuitionClassBuilder().withClassName("Sec 4 Chemistry") + .withClassTiming(SEC_CHEMISTRY_CLASS_TIMING).withRate("80").withLocation("Kumon at Orchard").build(); + + // Classes found in {@code CommandTestUtil} + public static final TuitionClass IB_PHYSICS = new TuitionClassBuilder().withClassName(VALID_CLASSNAME_IB_PHYSICS) + .withClassTiming(VALID_CLASSTIMING_IB_PHYSICS).withRate(VALID_RATE_IB_PHYSICS) + .withLocation(VALID_LOCATION_IB_PHYSICS).withStudentList(VALID_STUDENTLIST_IB_PHYSICS).build(); + public static final TuitionClass IB_MATHS = new TuitionClassBuilder().withClassName(VALID_CLASSNAME_IB_MATHS) + .withClassTiming(VALID_CLASSTIMING_IB_MATHS).withRate(VALID_RATE_IB_MATHS) + .withLocation(VALID_LOCATION_IB_MATHS).withStudentList(VALID_STUDENTLIST_IB_MATHS).build(); + + + private TypicalTimestable() { + } // prevents instantiation + + /** + * Returns a {@code TimesTable} with all the typical students and classes. + * Contains 7 students and 6 classes. + */ + public static TimesTable getTypicalTimesTable() { + TimesTable ab = new TimesTable(); + + for (Student student : getTypicalStudents()) { + ab.addStudent(student); + } + + for (TuitionClass tuitionClass : getTypicalClasses()) { + ab.addTuitionClass(tuitionClass); + } + + return ab; + } + + /** + * Returns an {@code TimesTable} with all the typical students in reversed order, and classes. + * Contains 7 students and 6 classes. + */ + public static TimesTable getTypicalTimesTableReverseStudents() { + TimesTable ab = new TimesTable(); + + List studentList = getTypicalStudents(); + + for (int i = 0; i < studentList.size(); i++) { + ab.addStudent(studentList.get(studentList.size() - 1 - i)); + } + + for (TuitionClass tuitionClass : getTypicalClasses()) { + ab.addTuitionClass(tuitionClass); + } + + return ab; + } + + /** + * Returns a {@code TimesTable} with all the typical students, and classes in reversed order. + * Contains 7 students and 6 classes. + */ + public static TimesTable getTypicalTimesTableReverseClasses() { + TimesTable ab = new TimesTable(); + + for (Student student : getTypicalStudents()) { + ab.addStudent(student); + } + + List tuitionClassList = getTypicalClasses(); + + for (int i = 0; i < tuitionClassList.size(); i++) { + ab.addTuitionClass(tuitionClassList.get(tuitionClassList.size() - 1 - i)); + } + + return ab; + } + + public static List getTypicalStudents() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } + + public static List getTypicalClasses() { + return new ArrayList<>(Arrays.asList(JC_PHYSICS, SEC_PHYSICS, JC_MATHS, SEC_MATHS, + JC_CHEMISTRY, SEC_CHEMISTRY)); + } +} diff --git a/src/test/java/seedu/times/ui/TabNameTest.java b/src/test/java/seedu/times/ui/TabNameTest.java new file mode 100644 index 00000000000..7dfcdd80e8a --- /dev/null +++ b/src/test/java/seedu/times/ui/TabNameTest.java @@ -0,0 +1,25 @@ +package seedu.times.ui; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +class TabNameTest { + + @Test + void getIndex() { + assertEquals(0, TabName.STUDENTS.getIndex()); + assertEquals(1, TabName.CLASSES.getIndex()); + assertEquals(2, TabName.TIMETABLE.getIndex()); + + assertNotEquals(1, TabName.STUDENTS.getIndex()); + assertNotEquals(2, TabName.STUDENTS.getIndex()); + + assertNotEquals(0, TabName.CLASSES.getIndex()); + assertNotEquals(2, TabName.CLASSES.getIndex()); + + assertNotEquals(0, TabName.TIMETABLE.getIndex()); + assertNotEquals(1, TabName.TIMETABLE.getIndex()); + } +} diff --git a/src/test/java/seedu/address/ui/TestFxmlObject.java b/src/test/java/seedu/times/ui/TestFxmlObject.java similarity index 96% rename from src/test/java/seedu/address/ui/TestFxmlObject.java rename to src/test/java/seedu/times/ui/TestFxmlObject.java index 5ecd82656f2..8c1b40df2db 100644 --- a/src/test/java/seedu/address/ui/TestFxmlObject.java +++ b/src/test/java/seedu/times/ui/TestFxmlObject.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.times.ui; import javafx.beans.DefaultProperty; diff --git a/src/test/java/seedu/address/ui/UiPartTest.java b/src/test/java/seedu/times/ui/UiPartTest.java similarity index 97% rename from src/test/java/seedu/address/ui/UiPartTest.java rename to src/test/java/seedu/times/ui/UiPartTest.java index 33d82d911b8..a4c4b8d5e66 100644 --- a/src/test/java/seedu/address/ui/UiPartTest.java +++ b/src/test/java/seedu/times/ui/UiPartTest.java @@ -1,8 +1,8 @@ -package seedu.address.ui; +package seedu.times.ui; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.times.testutil.Assert.assertThrows; import java.net.URL; import java.nio.file.Path; @@ -11,7 +11,7 @@ import org.junit.jupiter.api.io.TempDir; import javafx.fxml.FXML; -import seedu.address.MainApp; +import seedu.times.MainApp; public class UiPartTest { diff --git a/src/test/resources/view/UiPartTest/validFile.fxml b/src/test/resources/view/UiPartTest/validFile.fxml index bab836af0db..f1a34e64a78 100644 --- a/src/test/resources/view/UiPartTest/validFile.fxml +++ b/src/test/resources/view/UiPartTest/validFile.fxml @@ -1,4 +1,4 @@ - + Hello World! diff --git a/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml b/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml index 151e09ce926..f5260197330 100644 --- a/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml +++ b/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml @@ -1,6 +1,6 @@ - + Hello World!